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 style = Style.Current;
var rect = new Rectangle(Float2.Zero, Size); 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.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); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Options; using FlaxEditor.Options;
using FlaxEditor.SceneGraph;
using FlaxEditor.Windows; using FlaxEditor.Windows;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -52,14 +54,17 @@ namespace FlaxEditor.Content.GUI
public partial class ContentView : ContainerControl, IContentItemOwner public partial class ContentView : ContainerControl, IContentItemOwner
{ {
private readonly List<ContentItem> _items = new List<ContentItem>(256); 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 float _viewScale = 1.0f;
private ContentViewType _viewType = ContentViewType.Tiles; private ContentViewType _viewType = ContentViewType.Tiles;
private bool _isRubberBandSpanning = false; private bool _isRubberBandSpanning;
private Float2 _mousePresslocation; private Float2 _mousePressLocation;
private Rectangle _rubberBandRectangle; private Rectangle _rubberBandRectangle;
private bool _validDragOver;
private DragActors _dragActors;
#region External Events #region External Events
/// <summary> /// <summary>
@@ -193,6 +198,7 @@ namespace FlaxEditor.Content.GUI
OnDelete?.Invoke(_selection); OnDelete?.Invoke(_selection);
}), }),
new InputActionsContainer.Binding(options => options.SelectAll, SelectAll), new InputActionsContainer.Binding(options => options.SelectAll, SelectAll),
new InputActionsContainer.Binding(options => options.DeselectAll, DeselectAll),
new InputActionsContainer.Binding(options => options.Rename, () => new InputActionsContainer.Binding(options => options.Rename, () =>
{ {
if (HasSelection && _selection[0].CanRename) if (HasSelection && _selection[0].CanRename)
@@ -397,10 +403,7 @@ namespace FlaxEditor.Content.GUI
PerformLayout(); PerformLayout();
} }
/// <summary> private void BulkSelectUpdate(bool select = true)
/// Selects all the items.
/// </summary>
public void SelectAll()
{ {
// Lock layout // Lock layout
var wasLayoutLocked = IsLayoutLocked; var wasLayoutLocked = IsLayoutLocked;
@@ -408,13 +411,30 @@ namespace FlaxEditor.Content.GUI
// Select items // Select items
_selection.Clear(); _selection.Clear();
_selection.AddRange(_items); if (select)
_selection.AddRange(_items);
// Unload and perform UI layout // Unload and perform UI layout
IsLayoutLocked = wasLayoutLocked; IsLayoutLocked = wasLayoutLocked;
PerformLayout(); PerformLayout();
} }
/// <summary>
/// Selects all the items.
/// </summary>
public void SelectAll()
{
BulkSelectUpdate(true);
}
/// <summary>
/// Deselects all the items.
/// </summary>
public void DeselectAll()
{
BulkSelectUpdate(false);
}
/// <summary> /// <summary>
/// Deselects the specified item. /// Deselects the specified item.
/// </summary> /// </summary>
@@ -595,7 +615,9 @@ namespace FlaxEditor.Content.GUI
// Check if drag is over // Check if drag is over
if (IsDragOver && _validDragOver) 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 // 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); Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
} }
// Selection
if (_isRubberBandSpanning) if (_isRubberBandSpanning)
{ {
Render2D.FillRectangle(_rubberBandRectangle, Color.Orange * 0.4f); Render2D.FillRectangle(_rubberBandRectangle, style.Selection);
Render2D.DrawRectangle(_rubberBandRectangle, Color.Orange); Render2D.DrawRectangle(_rubberBandRectangle, style.SelectionBorder);
} }
} }
@@ -619,8 +642,8 @@ namespace FlaxEditor.Content.GUI
if (button == MouseButton.Left) if (button == MouseButton.Left)
{ {
_mousePresslocation = location; _mousePressLocation = location;
_rubberBandRectangle = new Rectangle(_mousePresslocation, 0, 0); _rubberBandRectangle = new Rectangle(_mousePressLocation, 0, 0);
_isRubberBandSpanning = true; _isRubberBandSpanning = true;
StartMouseCapture(); StartMouseCapture();
} }
@@ -632,8 +655,8 @@ namespace FlaxEditor.Content.GUI
{ {
if (_isRubberBandSpanning) if (_isRubberBandSpanning)
{ {
_rubberBandRectangle.Width = location.X - _mousePresslocation.X; _rubberBandRectangle.Width = location.X - _mousePressLocation.X;
_rubberBandRectangle.Height = location.Y - _mousePresslocation.Y; _rubberBandRectangle.Height = location.Y - _mousePressLocation.Y;
} }
base.OnMouseMove(location); base.OnMouseMove(location);
@@ -768,6 +791,114 @@ namespace FlaxEditor.Content.GUI
return false; 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 /> /// <inheritdoc />
protected override void PerformLayoutBeforeChildren() protected override void PerformLayoutBeforeChildren()
{ {
@@ -833,6 +964,9 @@ namespace FlaxEditor.Content.GUI
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()
{ {
if (IsDisposing)
return;
// Ensure to unlink all items // Ensure to unlink all items
ClearItems(); ClearItems();

View File

@@ -241,7 +241,12 @@ namespace FlaxEditor.Content
// Check if drag is over // Check if drag is over
if (IsDragOver && _validDragOver) 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) private bool ValidateDragItem(ContentItem item)

View File

@@ -75,6 +75,8 @@ namespace FlaxEditor.Content
/// <inheritdoc /> /// <inheritdoc />
public override void Create(string outputPath, object arg) public override void Create(string outputPath, object arg)
{ {
bool resetTransform = false;
var transform = Transform.Identity;
if (!(arg is Actor actor)) if (!(arg is Actor actor))
{ {
// Create default prefab root object // Create default prefab root object
@@ -86,8 +88,17 @@ namespace FlaxEditor.Content
// Cleanup it after usage // Cleanup it after usage
Object.Destroy(actor, 20.0f); 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); PrefabManager.CreatePrefab(actor, outputPath, true);
if (resetTransform)
actor.LocalTransform = transform;
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -747,7 +747,7 @@ namespace FlaxEditor.CustomEditors
/// </summary> /// </summary>
public void SetValueToDefault() public void SetValueToDefault()
{ {
SetValueCloned(Values.DefaultValue); SetValue(Utilities.Utils.CloneValue(Values.DefaultValue));
} }
/// <summary> /// <summary>
@@ -784,19 +784,7 @@ namespace FlaxEditor.CustomEditors
return; return;
} }
SetValueCloned(Values.ReferenceValue); SetValue(Utilities.Utils.CloneValue(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);
} }
/// <summary> /// <summary>

View File

@@ -245,9 +245,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{ {
// Special case for new Script added to actor // Special case for new Script added to actor
if (editor.Values[0] is Script script && !script.HasPrefabLink) if (editor.Values[0] is Script script && !script.HasPrefabLink)
{
return CreateDiffNode(editor); return CreateDiffNode(editor);
}
// Skip if no change detected // Skip if no change detected
var isRefEdited = editor.Values.IsReferenceValueModified; var isRefEdited = editor.Values.IsReferenceValueModified;
@@ -258,9 +256,16 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (editor.ChildrenEditors.Count == 0 || (isRefEdited && editor is CollectionEditor)) if (editor.ChildrenEditors.Count == 0 || (isRefEdited && editor is CollectionEditor))
result = CreateDiffNode(editor); result = CreateDiffNode(editor);
bool isScriptEditorWithRefValue = editor is ScriptsEditor && editor.Values.HasReferenceValue; 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++) 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 (child != null)
{ {
if (result == null) if (result == null)
@@ -276,7 +281,6 @@ namespace FlaxEditor.CustomEditors.Dedicated
{ {
var prefabObjectScript = prefabObjectScripts[j]; var prefabObjectScript = prefabObjectScripts[j];
bool isRemoved = true; bool isRemoved = true;
for (int i = 0; i < editor.ChildrenEditors.Count; i++) for (int i = 0; i < editor.ChildrenEditors.Count; i++)
{ {
if (editor.ChildrenEditors[i].Values is ScriptsEditor.ScriptsContainer container && container.PrefabObjectId == prefabObjectScript.PrefabObjectID) if (editor.ChildrenEditors[i].Values is ScriptsEditor.ScriptsContainer container && container.PrefabObjectId == prefabObjectScript.PrefabObjectID)
@@ -286,14 +290,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
break; break;
} }
} }
if (isRemoved) if (isRemoved)
{ {
var dummy = new RemovedScriptDummy var dummy = new RemovedScriptDummy
{ {
PrefabObject = prefabObjectScript PrefabObject = prefabObjectScript
}; };
var child = CreateDiffNode(dummy); var child = CreateDiffNode(dummy);
if (result == null) if (result == null)
result = CreateDiffNode(editor); result = CreateDiffNode(editor);

View File

@@ -67,25 +67,6 @@ public class MissingScriptEditor : GenericEditor
base.Initialize(layout); 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) private void RunReplacementMultiCast(List<IUndoAction> actions)
{ {
if (actions.Count == 0) 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>(); var missingScripts = new List<MissingScript>();
if (!replaceAllInScene) var currentMissing = (MissingScript)Values[0];
missingScripts.Add((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 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) foreach (var missingScript in missingScripts)
actions.Add(AddRemoveScript.Add(missingScript.Actor, script)); actions.Add(AddRemoveScript.Add(missingScript.Actor, script));
RunReplacementMultiCast(actions); RunReplacementMultiCast(actions);
@@ -149,7 +168,7 @@ public class MissingScriptEditor : GenericEditor
var cm = new ItemsListContextMenu(180); var cm = new ItemsListContextMenu(180);
for (int i = 0; i < scripts.Count; i++) for (int i = 0; i < scripts.Count; i++)
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[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.SortItems();
cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0)); 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>(); var actions = new List<IUndoAction>();
foreach (var body in bodies) 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(); action.Do();
actions.Add(action); actions.Add(action);
} }
@@ -185,7 +185,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
var body = bodies.FirstOrDefault(x => x.Name == name); var body = bodies.FirstOrDefault(x => x.Name == name);
if (body != null) if (body != null)
{ {
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(body.ID) }); var action = new Actions.DeleteActorsAction(body);
action.Do(); action.Do();
Presenter.Undo?.AddAction(action); Presenter.Undo?.AddAction(action);
} }
@@ -224,7 +224,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
else else
{ {
// Remove joint that will no longer be valid // 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(); action.Do();
Presenter.Undo?.AddAction(action); Presenter.Undo?.AddAction(action);
} }
@@ -233,7 +233,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Remove body // Remove body
{ {
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(body.ID) }); var action = new Actions.DeleteActorsAction(body);
action.Do(); action.Do();
Presenter.Undo?.AddAction(action); Presenter.Undo?.AddAction(action);
} }

View File

@@ -151,8 +151,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag) if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag)
{ {
var area = new Rectangle(Float2.Zero, size); var area = new Rectangle(Float2.Zero, size);
Render2D.FillRectangle(area, Color.Orange * 0.5f); Render2D.FillRectangle(area, style.Selection);
Render2D.DrawRectangle(area, Color.Black); Render2D.DrawRectangle(area, style.SelectionBorder);
} }
base.Draw(); base.Draw();
@@ -520,7 +520,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
{ {
base.Draw(); 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); Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), color);
} }

View File

@@ -589,25 +589,27 @@ namespace FlaxEditor.CustomEditors.Dedicated
LayoutElementsContainer yEl; LayoutElementsContainer yEl;
LayoutElementsContainer hEl; LayoutElementsContainer hEl;
LayoutElementsContainer vEl; LayoutElementsContainer vEl;
Color axisColorX = ActorTransformEditor.AxisColorX;
Color axisColorY = ActorTransformEditor.AxisColorY;
if (xEq) if (xEq)
{ {
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values)); xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values)); vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX);
} }
else else
{ {
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values)); xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX);
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values)); vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX);
} }
if (yEq) if (yEq)
{ {
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values)); yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values)); hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY);
} }
else else
{ {
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values)); yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY);
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values)); hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY);
} }
xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y); xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y);
xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.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) private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont)
{ {
var horUp = cont.VerticalPanel(); var panel = cont.VerticalPanel();
horUp.Panel.Margin = Margin.Zero; panel.Panel.Margin = Margin.Zero;
return horUp; return panel;
} }
private CustomElementsContainer<UniformGridPanel> UniformGridTwoByOne(LayoutElementsContainer cont) private CustomElementsContainer<UniformGridPanel> UniformGridTwoByOne(LayoutElementsContainer cont)
{ {
var horUp = cont.CustomContainer<UniformGridPanel>(); var grid = cont.CustomContainer<UniformGridPanel>();
horUp.CustomControl.SlotsHorizontally = 2; grid.CustomControl.SlotsHorizontally = 2;
horUp.CustomControl.SlotsVertically = 1; grid.CustomControl.SlotsVertically = 1;
horUp.CustomControl.SlotPadding = Margin.Zero; grid.CustomControl.SlotPadding = Margin.Zero;
horUp.CustomControl.ClipChildren = false; grid.CustomControl.ClipChildren = false;
return horUp; 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); var grid = UniformGridTwoByOne(el);
hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0); grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1);
LabelElement lab = hor.Label(text); var label = grid.Label(text);
hor.Object(values); var editor = grid.Object(values);
return hor; 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; private bool _cachedXEq;

View File

@@ -26,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors
/// </summary> /// </summary>
public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f); 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> /// <summary>
/// Custom editor for actor position property. /// Custom editor for actor position property.
/// </summary> /// </summary>
@@ -39,12 +44,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Override colors // Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f; XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX; XElement.ValueBox.BorderSelectedColor = AxisColorX;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY; YElement.ValueBox.BorderSelectedColor = AxisColorY;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ; ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
} }
} }
@@ -62,12 +66,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Override colors // Override colors
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
var grayOutFactor = 0.6f; XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
XElement.ValueBox.BorderSelectedColor = AxisColorX; XElement.ValueBox.BorderSelectedColor = AxisColorX;
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
YElement.ValueBox.BorderSelectedColor = AxisColorY; YElement.ValueBox.BorderSelectedColor = AxisColorY;
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
ZElement.ValueBox.BorderSelectedColor = AxisColorZ; ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
} }
} }

View File

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

View File

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

View File

@@ -220,7 +220,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Check if drag is over // Check if drag is over
if (IsDragOver && _hasValidDragOver) 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 /> /// <inheritdoc />

View File

@@ -175,7 +175,11 @@ namespace FlaxEditor.CustomEditors.Editors
// Check if drag is over // Check if drag is over
if (IsDragOver && _hasValidDragOver) 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 /> /// <inheritdoc />

View File

@@ -1676,6 +1676,9 @@ namespace FlaxEditor
[return: MarshalAs(UnmanagedType.U1)] [return: MarshalAs(UnmanagedType.U1)]
internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot); 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))] [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
internal static partial float Internal_GetAnimationTime(IntPtr animatedModel); internal static partial float Internal_GetAnimationTime(IntPtr animatedModel);

View File

@@ -182,7 +182,11 @@ namespace FlaxEditor.GUI
// Check if drag is over // Check if drag is over
if (IsDragOver && _dragOverElement != null && _dragOverElement.HasValidDrag) 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 /> /// <inheritdoc />

View File

@@ -359,6 +359,17 @@ namespace FlaxEditor.GUI.ContextMenu
ButtonClicked?.Invoke(button); 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 /> /// <inheritdoc />
public override bool ContainsPoint(ref Float2 location, bool precise) public override bool ContainsPoint(ref Float2 location, bool precise)
{ {

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Options;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; 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> /// <summary>
/// Selects all keyframes. /// Selects all keyframes.
/// </summary> /// </summary>
public void SelectAll() public void SelectAll()
{ {
for (int i = 0; i < _points.Count; i++) BulkSelectUpdate(true);
{ }
_points[i].IsSelected = true;
} /// <summary>
/// Deselects all keyframes.
/// </summary>
public void DeselectAll()
{
BulkSelectUpdate(false);
} }
/// <summary> /// <summary>
@@ -899,8 +913,8 @@ namespace FlaxEditor.GUI
_mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)), _mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)),
_mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos)) _mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos))
); );
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f); Render2D.FillRectangle(selectionRect, style.Selection);
Render2D.DrawRectangle(selectionRect, Color.Orange); Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
} }
base.Draw(); base.Draw();
@@ -926,34 +940,35 @@ namespace FlaxEditor.GUI
if (base.OnKeyDown(key)) if (base.OnKeyDown(key))
return true; 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(); RemoveKeyframes();
return true; 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; return false;
} }

View File

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

View File

@@ -122,6 +122,10 @@ namespace FlaxEditor.GUI
if (IsMouseOver || IsFocused) if (IsMouseOver || IsFocused)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted); 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 // Draw all highlights
if (_highlights != null) if (_highlights != null)
{ {
@@ -262,6 +266,10 @@ namespace FlaxEditor.GUI
} }
} }
category.Visible = anyVisible; 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 var categoryPanel = new DropPanel
{ {
HeaderText = item.Category, 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, Parent = parent,
}; };
categoryPanel.Open(false); categoryPanel.Close(false);
_categoryPanels.Add(categoryPanel); _categoryPanels.Add(categoryPanel);
parent = categoryPanel; parent = categoryPanel;
} }
@@ -382,6 +395,7 @@ namespace FlaxEditor.GUI
item2.UpdateFilter(null); item2.UpdateFilter(null);
} }
category.Visible = true; category.Visible = true;
category.Close(false);
} }
} }

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Text; using System.Text;
using FlaxEditor.CustomEditors; using FlaxEditor.CustomEditors;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Options;
using FlaxEditor.Scripting; using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
@@ -432,6 +433,7 @@ namespace FlaxEditor.GUI
if (_editor.EnableKeyframesValueEdit) if (_editor.EnableKeyframesValueEdit)
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location)); cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
cm.AddButton("Select all keyframes", _editor.SelectAll).Enabled = _editor._points.Count > 0; 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", () => cm.AddButton("Copy all keyframes", () =>
{ {
_editor.SelectAll(); _editor.SelectAll();
@@ -902,7 +904,7 @@ namespace FlaxEditor.GUI
var k = new Keyframe var k = new Keyframe
{ {
Time = keyframesPos.X, Time = keyframesPos.X,
Value = DefaultValue, Value = Utilities.Utils.CloneValue(DefaultValue),
}; };
OnEditingStart(); OnEditingStart();
AddKeyframe(k); 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> /// <summary>
/// Selects all keyframes. /// Selects all keyframes.
/// </summary> /// </summary>
public void SelectAll() public void SelectAll()
{ {
for (int i = 0; i < _points.Count; i++) BulkSelectUpdate(true);
{ }
_points[i].IsSelected = true;
} /// <summary>
/// Deselects all keyframes.
/// </summary>
public void DeselectAll()
{
BulkSelectUpdate(false);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -1241,8 +1256,8 @@ namespace FlaxEditor.GUI
_mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)), _mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)),
_mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos)) _mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos))
); );
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f); Render2D.FillRectangle(selectionRect, style.Selection);
Render2D.DrawRectangle(selectionRect, Color.Orange); Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
} }
base.Draw(); base.Draw();
@@ -1268,33 +1283,33 @@ namespace FlaxEditor.GUI
if (base.OnKeyDown(key)) if (base.OnKeyDown(key))
return true; 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(); RemoveKeyframes();
return true; 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; return false;
} }

View File

@@ -304,7 +304,9 @@ namespace FlaxEditor.GUI.Timeline
if (IsDragOver && _currentDragEffect != DragDropEffect.None) if (IsDragOver && _currentDragEffect != DragDropEffect.None)
{ {
var style = Style.Current; 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(); base.Draw();

View File

@@ -187,6 +187,11 @@ namespace FlaxEditor.GUI.Timeline
private bool _showPreviewValues = true; private bool _showPreviewValues = true;
private PlaybackStates _state = PlaybackStates.Disabled; 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> /// <summary>
/// Flag used to mark modified timeline data. /// Flag used to mark modified timeline data.
/// </summary> /// </summary>

View File

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

View File

@@ -204,7 +204,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
b.TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor); 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> /// <summary>

View File

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

View File

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

View File

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

View File

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

View File

@@ -437,8 +437,6 @@ namespace FlaxEditor.Gizmo
{ {
case Mode.Translate: case Mode.Translate:
UpdateTranslateScale(); UpdateTranslateScale();
if (Owner.SnapToVertex)
UpdateVertexSnapping();
break; break;
case Mode.Scale: case Mode.Scale:
UpdateTranslateScale(); UpdateTranslateScale();
@@ -447,6 +445,8 @@ namespace FlaxEditor.Gizmo
UpdateRotate(dt); UpdateRotate(dt);
break; break;
} }
if (Owner.SnapToVertex)
UpdateVertexSnapping();
} }
else else
{ {
@@ -553,41 +553,21 @@ namespace FlaxEditor.Gizmo
for (int i = 0; i < SelectionCount; i++) for (int i = 0; i < SelectionCount; i++)
{ {
var obj = GetSelectedObject(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; closestDistance = distance;
closestObject = obj; closestObject = obj;
} }
} }
if (closestObject == null) if (closestObject == null)
{ 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 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;
// Find the closest vertex to bounding box point (collision detection approximation) _vertexSnapObject = closestObject;
var closestPoint = ray.Ray.GetPoint(closestDistance); if (!closestObject.OnVertexSnap(ref ray.Ray, closestDistance, out _vertexSnapPoint))
if (!closestObject.OnVertexSnap(ref closestPoint, out _vertexSnapPoint))
{ {
// Failed to get the closest point // The OnVertexSnap is unimplemented or failed to get point return because there is nothing to do
_vertexSnapPoint = closestPoint; _vertexSnapPoint = Vector3.Zero;
return;
} }
// Transform back to the local space of the object to work when moving it // 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++) for (int i = 0; i < SelectionCount; i++)
rayCast.ExcludeObjects.Add(GetSelectedObject(i)); rayCast.ExcludeObjects.Add(GetSelectedObject(i));
var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _); 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 rayCast.Ray, distance, out var pointSnapped)
if (hit.OnVertexSnap(ref point, out var pointSnapped)
//&& Vector3.Distance(point, pointSnapped) <= 25.0f //&& Vector3.Distance(point, pointSnapped) <= 25.0f
) )
{ {
_vertexSnapObjectTo = hit; _vertexSnapObjectTo = hit;
_vertexSnapPointTo = hit.Transform.WorldToLocal(pointSnapped); _vertexSnapPointTo = hit.Transform.WorldToLocal(pointSnapped);
@@ -712,5 +691,12 @@ namespace FlaxEditor.Gizmo
protected virtual void OnDuplicate() 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; 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) DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel)
{ {
return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f; 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() void ManagedEditor::Exit()
{ {
if (WasExitCalled) if (WasExitCalled)
{
// Ups xD
LOG(Warning, "Managed Editor exit called after exit or before init.");
return; return;
}
// Set flag
WasExitCalled = true; WasExitCalled = true;
// Skip if managed object is missing // Skip if managed object is missing

View File

@@ -225,8 +225,15 @@ namespace FlaxEditor.Modules
throw new ArgumentException("Missing prefab to apply."); throw new ArgumentException("Missing prefab to apply.");
PrefabApplying?.Invoke(prefab, instance); 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 // 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."); throw new Exception("Failed to apply the prefab. See log to learn more.");
PrefabApplied?.Invoke(prefab, instance); 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> /// <summary>
/// Selects all scenes. /// Selects all scenes.
/// </summary> /// </summary>
public void SelectAllScenes() public void SelectAllScenes()
{ {
// Select all scenes (linked to the root node) BulkScenesSelectUpdate(true);
Select(Editor.Scene.Root.ChildNodes); }
/// <summary>
/// Deselects all scenes.
/// </summary>
public void DeselectAllScenes()
{
BulkScenesSelectUpdate(false);
} }
/// <summary> /// <summary>
@@ -320,7 +333,7 @@ namespace FlaxEditor.Modules
actorNode.PostSpawn(); actorNode.PostSpawn();
// Create undo action // Create undo action
IUndoAction action = new DeleteActorsAction(new List<SceneGraphNode>(1) { actorNode }, true); IUndoAction action = new DeleteActorsAction(actorNode, true);
if (autoSelect) if (autoSelect)
{ {
var before = Selection.ToArray(); var before = Selection.ToArray();

View File

@@ -6,6 +6,7 @@ using System.IO;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.Actors; using FlaxEditor.SceneGraph.Actors;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object; using Object = FlaxEngine.Object;
namespace FlaxEditor.Modules namespace FlaxEditor.Modules
@@ -454,6 +455,41 @@ namespace FlaxEditor.Modules
Profiler.EndEvent(); 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) private void OnSceneLoaded(Scene scene, Guid sceneId)
{ {
var startTime = DateTime.UtcNow; var startTime = DateTime.UtcNow;
@@ -659,6 +695,9 @@ namespace FlaxEditor.Modules
Root = new ScenesRootNode(); Root = new ScenesRootNode();
// Bind events // Bind events
Level.SceneSaving += OnSceneSaving;
Level.SceneSaved += OnSceneSaved;
Level.SceneSaveError += OnSceneSaveError;
Level.SceneLoaded += OnSceneLoaded; Level.SceneLoaded += OnSceneLoaded;
Level.SceneUnloading += OnSceneUnloading; Level.SceneUnloading += OnSceneUnloading;
Level.ActorSpawned += OnActorSpawned; Level.ActorSpawned += OnActorSpawned;
@@ -673,6 +712,9 @@ namespace FlaxEditor.Modules
public override void OnExit() public override void OnExit()
{ {
// Unbind events // Unbind events
Level.SceneSaving -= OnSceneSaving;
Level.SceneSaved -= OnSceneSaved;
Level.SceneSaveError -= OnSceneSaveError;
Level.SceneLoaded -= OnSceneLoaded; Level.SceneLoaded -= OnSceneLoaded;
Level.SceneUnloading -= OnSceneUnloading; Level.SceneUnloading -= OnSceneUnloading;
Level.ActorSpawned -= OnActorSpawned; Level.ActorSpawned -= OnActorSpawned;

View File

@@ -264,11 +264,14 @@ namespace FlaxEditor.Modules
_enterPlayFocusedWindow = gameWin; _enterPlayFocusedWindow = gameWin;
// Show Game widow if hidden // 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"); Editor.Log("[PlayMode] Enter");
} }

View File

@@ -54,6 +54,7 @@ namespace FlaxEditor.Modules
private ContextMenuButton _menuEditDelete; private ContextMenuButton _menuEditDelete;
private ContextMenuButton _menuEditDuplicate; private ContextMenuButton _menuEditDuplicate;
private ContextMenuButton _menuEditSelectAll; private ContextMenuButton _menuEditSelectAll;
private ContextMenuButton _menuEditDeselectAll;
private ContextMenuButton _menuEditFind; private ContextMenuButton _menuEditFind;
private ContextMenuButton _menuSceneMoveActorToViewport; private ContextMenuButton _menuSceneMoveActorToViewport;
private ContextMenuButton _menuSceneAlignActorWithViewport; private ContextMenuButton _menuSceneAlignActorWithViewport;
@@ -554,6 +555,7 @@ namespace FlaxEditor.Modules
_menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
cm.AddSeparator(); cm.AddSeparator();
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); _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); _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors);
_menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
cm.AddSeparator(); cm.AddSeparator();
@@ -673,6 +675,7 @@ namespace FlaxEditor.Modules
_menuEditDelete.ShortKeys = inputOptions.Delete.ToString(); _menuEditDelete.ShortKeys = inputOptions.Delete.ToString();
_menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString(); _menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString();
_menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString(); _menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString();
_menuEditDeselectAll.ShortKeys = inputOptions.DeselectAll.ToString();
_menuEditFind.ShortKeys = inputOptions.Search.ToString(); _menuEditFind.ShortKeys = inputOptions.Search.ToString();
_menuGamePlayGame.ShortKeys = inputOptions.Play.ToString(); _menuGamePlayGame.ShortKeys = inputOptions.Play.ToString();
_menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString(); _menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString();
@@ -739,6 +742,17 @@ namespace FlaxEditor.Modules
playActionGroup.SelectedChanged = SetPlayAction; playActionGroup.SelectedChanged = SetPlayAction;
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; }; 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})"); _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"); _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; _menuEditDelete.Enabled = hasSthSelected;
_menuEditDuplicate.Enabled = hasSthSelected; _menuEditDuplicate.Enabled = hasSthSelected;
_menuEditSelectAll.Enabled = Level.IsAnySceneLoaded; _menuEditSelectAll.Enabled = Level.IsAnySceneLoaded;
_menuEditDeselectAll.Enabled = hasSthSelected;
control.PerformLayout(); control.PerformLayout();
} }
@@ -1043,6 +1058,13 @@ namespace FlaxEditor.Modules
Editor.Options.Apply(options); 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() private void OnMainWindowClosing()
{ {
// Clear UI references (GUI cannot be used after window closing) // Clear UI references (GUI cannot be used after window closing)

View File

@@ -56,6 +56,10 @@ namespace FlaxEditor.Options
[EditorDisplay("Common"), EditorOrder(190)] [EditorDisplay("Common"), EditorOrder(190)]
public InputBinding SelectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Control); 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")] [DefaultValue(typeof(InputBinding), "F")]
[EditorDisplay("Common"), EditorOrder(200)] [EditorDisplay("Common"), EditorOrder(200)]
public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F); public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F);

View File

@@ -90,6 +90,32 @@ namespace FlaxEditor.Options
PlayScenes, 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> /// <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. /// 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> /// </summary>
@@ -229,6 +255,13 @@ namespace FlaxEditor.Options
[EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)] [EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)]
public PlayAction PlayButtonAction { get; set; } = PlayAction.PlayScenes; 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> /// <summary>
/// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game. /// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game.
/// </summary> /// </summary>

View File

@@ -212,6 +212,14 @@ namespace FlaxEditor.Options
string styleName = themeOptions.SelectedStyle; string styleName = themeOptions.SelectedStyle;
if (styleName != ThemeOptions.DefaultName && styleName != ThemeOptions.LightDefault && themeOptions.Styles.TryGetValue(styleName, out var style) && style != null) 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; Style.Current = style;
} }
else else
@@ -258,6 +266,8 @@ namespace FlaxEditor.Options
TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46), TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46),
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC), CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
ProgressNormal = Color.FromBgra(0xFF0ad328), ProgressNormal = Color.FromBgra(0xFF0ad328),
Selection = Color.Orange * 0.4f,
SelectionBorder = Color.Orange,
Statusbar = new Style.StatusbarStyle Statusbar = new Style.StatusbarStyle
{ {
@@ -318,6 +328,8 @@ namespace FlaxEditor.Options
TextBoxBackgroundSelected = new Color(0.73f, 0.73f, 0.80f, 1f), TextBoxBackgroundSelected = new Color(0.73f, 0.73f, 0.80f, 1f),
CollectionBackgroundColor = new Color(0.85f, 0.85f, 0.88f, 1f), CollectionBackgroundColor = new Color(0.85f, 0.85f, 0.88f, 1f),
ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f), ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f),
Selection = Color.Orange * 0.4f,
SelectionBorder = Color.Orange,
// Fonts // Fonts
FontTitle = options.Interface.TitleFont.GetFont(), FontTitle = options.Interface.TitleFont.GetFont(),

View File

@@ -316,41 +316,7 @@ namespace FlaxEditor.SceneGraph
{ {
base.OnParentChanged(); base.OnParentChanged();
// Update UI (special case if actor is spawned and added to existing scene tree) _treeNode.OnParentChanged(_actor, parentNode as ActorNode);
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;
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -1,5 +1,11 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. // 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;
using System.Collections.Generic; using System.Collections.Generic;
using FlaxEditor.Content; using FlaxEditor.Content;
@@ -25,18 +31,20 @@ namespace FlaxEditor.SceneGraph.Actors
} }
/// <inheritdoc /> /// <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; var model = ((StaticModel)Actor).Model;
if (model && !model.WaitForLoaded()) if (model && !model.WaitForLoaded())
{ {
// TODO: move to C++ and use cached vertex buffer internally inside the Mesh // TODO: move to C++ and use cached vertex buffer internally inside the Mesh
if (_vertices == null) if (_vertices == null)
_vertices = new(); _vertices = new();
var pointLocal = (Float3)Actor.Transform.WorldToLocal(point); var pointLocal = (Float3)Actor.Transform.WorldToLocal(result);
var minDistance = float.MaxValue; var minDistance = Real.MaxValue;
foreach (var lod in model.LODs) var lodIndex = 0; // TODO: use LOD index based on the game view
var lod = model.LODs[lodIndex];
{ {
var hit = false; var hit = false;
foreach (var mesh in lod.Meshes) foreach (var mesh in lod.Meshes)

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.GUI; using FlaxEditor.GUI;
using FlaxEditor.GUI.Drag; using FlaxEditor.GUI.Drag;
@@ -14,7 +15,6 @@ using FlaxEditor.Windows.Assets;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.SceneGraph.GUI namespace FlaxEditor.SceneGraph.GUI
{ {
@@ -82,8 +82,51 @@ namespace FlaxEditor.SceneGraph.GUI
UpdateText(); 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() 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) if (Parent is ActorTreeNode parent)
{ {
var anyChanged = false; var anyChanged = false;
@@ -419,134 +462,6 @@ namespace FlaxEditor.SceneGraph.GUI
_dragHandlers.OnDragLeave(); _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 /> /// <inheritdoc />
protected override DragDropEffect OnDragDropHeader(DragData data) protected override DragDropEffect OnDragDropHeader(DragData data)
{ {
@@ -593,46 +508,24 @@ namespace FlaxEditor.SceneGraph.GUI
// Drag actors // Drag actors
if (_dragActors != null && _dragActors.HasValidDrag) if (_dragActors != null && _dragActors.HasValidDrag)
{ {
bool worldPositionLock = Root.GetKey(KeyboardKeys.Control) == false; bool worldPositionsStays = Root.GetKey(KeyboardKeys.Control) == false;
var singleObject = _dragActors.Objects.Count == 1; var objects = new SceneObject[_dragActors.Objects.Count];
if (singleObject) for (int i = 0; i < objects.Length; i++)
{ objects[i] = _dragActors.Objects[i].Actor;
var targetActor = _dragActors.Objects[0].Actor; var action = new ParentActorsAction(objects, newParent, newOrder, worldPositionsStays);
var customAction = targetActor.HasPrefabLink ? new ReparentAction(targetActor) : null; ActorNode.Root.Undo?.AddAction(action);
using (new UndoBlock(ActorNode.Root.Undo, targetActor, "Change actor parent", customAction)) action.Do();
{
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;
}
}
}
result = DragDropEffect.Move; result = DragDropEffect.Move;
} }
// Drag scripts // Drag scripts
else if (_dragScripts != null && _dragScripts.HasValidDrag) else if (_dragScripts != null && _dragScripts.HasValidDrag)
{ {
foreach (var script in _dragScripts.Objects) var objects = new SceneObject[_dragScripts.Objects.Count];
{ for (int i = 0; i < objects.Length; i++)
var customAction = script.HasPrefabLink ? new ReparentAction(script) : null; objects[i] = _dragScripts.Objects[i];
using (new UndoBlock(ActorNode.Root.Undo, script, "Change script parent", customAction)) var action = new ParentActorsAction(objects, newParent, newOrder);
{ ActorNode.Root.Undo?.AddAction(action);
script.SetParent(newParent, true); action.Do();
}
}
Select(); Select();
result = DragDropEffect.Move; result = DragDropEffect.Move;
} }

View File

@@ -41,7 +41,11 @@ namespace FlaxEditor.SceneGraph
protected SceneGraphNode(Guid id) protected SceneGraphNode(Guid id)
{ {
ID = 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> /// <summary>
@@ -94,18 +98,6 @@ namespace FlaxEditor.SceneGraph
/// </summary> /// </summary>
public virtual bool CanTransform => true; 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> /// <summary>
/// Gets a value indicating whether this <see cref="SceneGraphNode"/> is active. /// Gets a value indicating whether this <see cref="SceneGraphNode"/> is active.
/// </summary> /// </summary>
@@ -365,14 +357,15 @@ namespace FlaxEditor.SceneGraph
} }
/// <summary> /// <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> /// </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> /// <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> /// <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; return false;
} }

View File

@@ -92,11 +92,12 @@ namespace FlaxEditor.SceneGraph
private static void FillTree(SceneGraphNode node, List<SceneGraphNode> result) private static void FillTree(SceneGraphNode node, List<SceneGraphNode> result)
{ {
result.AddRange(node.ChildNodes); if (result.Contains(node))
for (int i = 0; i < node.ChildNodes.Count; i++) return;
{ result.Add(node);
FillTree(node.ChildNodes[i], result); var children = node.ChildNodes;
} for (int i = 0; i < children.Count; i++)
FillTree(children[i], result);
} }
/// <summary> /// <summary>
@@ -109,21 +110,9 @@ namespace FlaxEditor.SceneGraph
{ {
if (nodes == null || result == null) if (nodes == null || result == null)
throw new ArgumentNullException(); throw new ArgumentNullException();
result.Clear(); result.Clear();
for (var i = 0; i < nodes.Count; i++) for (var i = 0; i < nodes.Count; i++)
{ FillTree(nodes[i], result);
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);
}
} }
/// <summary> /// <summary>
@@ -150,7 +139,6 @@ namespace FlaxEditor.SceneGraph
if (node == null || result == null) if (node == null || result == null)
throw new ArgumentNullException(); throw new ArgumentNullException();
result.Clear(); result.Clear();
result.Add(node);
FillTree(node, result); FillTree(node, result);
} }
} }

View File

@@ -602,7 +602,7 @@ namespace FlaxEditor.Surface.Archetypes
var dataB = (Guid)_node.Values[5 + i * 2]; var dataB = (Guid)_node.Values[5 + i * 2];
pointsAnims[i] = dataB; 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; _animationX.Value = 0.0f;
} }
var ranges = (Float4)Values[0];
_animationX.MinValue = ranges.X;
_animationX.MaxValue = ranges.Y;
_animationXLabel.Enabled = isValid; _animationXLabel.Enabled = isValid;
_animationX.Enabled = isValid; _animationX.Enabled = isValid;
} }
@@ -732,7 +735,7 @@ namespace FlaxEditor.Surface.Archetypes
var dataB = (Guid)_node.Values[5 + i * 2]; var dataB = (Guid)_node.Values[5 + i * 2];
pointsAnims[i] = dataB; 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; _animationX.Value = 0.0f;
_animationY.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; _animationXLabel.Enabled = isValid;
_animationX.Enabled = isValid; _animationX.Enabled = isValid;
_animationYLabel.Enabled = isValid; _animationYLabel.Enabled = isValid;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
@@ -25,6 +24,9 @@ namespace FlaxEditor.Actions
[Serialize] [Serialize]
private Guid[] _nodeParentsIDs; private Guid[] _nodeParentsIDs;
[Serialize]
private int[] _nodeParentsOrders;
[Serialize] [Serialize]
private Guid[] _prefabIds; private Guid[] _prefabIds;
@@ -43,12 +45,35 @@ namespace FlaxEditor.Actions
[Serialize] [Serialize]
protected List<SceneGraphNode> _nodeParents; 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> /// <summary>
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class. /// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
/// </summary> /// </summary>
/// <param name="nodes">The objects.</param> /// <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> /// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
internal DeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false) /// <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; _isInverted = isInverted;
@@ -82,6 +107,12 @@ namespace FlaxEditor.Actions
_nodeParentsIDs = new Guid[_nodeParents.Count]; _nodeParentsIDs = new Guid[_nodeParents.Count];
for (int i = 0; i < _nodeParentsIDs.Length; i++) for (int i = 0; i < _nodeParentsIDs.Length; i++)
_nodeParentsIDs[i] = _nodeParents[i].ID; _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 // Serialize actors
_actorsData = Actor.ToBytes(actors.ToArray()); _actorsData = Actor.ToBytes(actors.ToArray());
@@ -122,6 +153,7 @@ namespace FlaxEditor.Actions
{ {
_actorsData = null; _actorsData = null;
_nodeParentsIDs = null; _nodeParentsIDs = null;
_nodeParentsOrders = null;
_prefabIds = null; _prefabIds = null;
_prefabObjectIds = null; _prefabObjectIds = null;
_nodeParents.Clear(); _nodeParents.Clear();
@@ -211,6 +243,8 @@ namespace FlaxEditor.Actions
if (foundNode is ActorNode node) if (foundNode is ActorNode node)
{ {
nodes.Add(node); nodes.Add(node);
if (_nodeParentsOrders != null)
node.Actor.OrderInParent = _nodeParentsOrders[i];
} }
} }
nodes.BuildNodesParents(_nodeParents); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEngine; using FlaxEngine;
@@ -165,7 +164,7 @@ namespace FlaxEditor.Actions
var child = children[j]; var child = children[j];
if (child != actor && child.Name == name) 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; foundNamesResults[newName] = true;
actor.Name = newName; actor.Name = newName;
// Multiple actors may have the same name, continue // Multiple actors may have the same name, continue

View File

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

View File

@@ -11,7 +11,6 @@ using System.Globalization;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -20,6 +19,7 @@ using FlaxEditor.GUI.Input;
using FlaxEditor.GUI.Tree; using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph;
using FlaxEngine; using FlaxEngine;
using FlaxEngine.Json;
using FlaxEngine.GUI; using FlaxEngine.GUI;
using FlaxEngine.Utilities; using FlaxEngine.Utilities;
using FlaxEditor.Windows; 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> /// <summary>
/// The colors for the keyframes used by the curve editor. /// The colors for the keyframes used by the curve editor.
/// </summary> /// </summary>
@@ -1289,6 +1305,7 @@ namespace FlaxEditor.Utilities
inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste); inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste);
inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate); inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate);
inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes); 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.Delete, Editor.Instance.SceneEditing.Delete);
inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search()); inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search());
inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport); inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport);

View File

@@ -2,7 +2,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content; using FlaxEditor.Content;
using FlaxEditor.Gizmo; using FlaxEditor.Gizmo;
using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.ContextMenu;
@@ -591,15 +590,32 @@ namespace FlaxEditor.Viewport
base.Draw(); base.Draw();
// Selected UI controls outline // 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++) 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 control = controlActor.Control;
var bounds = Rectangle.FromPoints(control.PointToParent(this, Float2.Zero), control.PointToParent(this, control.Size)); var bounds = control.EditorBounds;
Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize); 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 /> /// <inheritdoc />
@@ -799,6 +815,15 @@ namespace FlaxEditor.Viewport
{ {
base.OnDebugDraw(context, ref renderContext); 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 unsafe
{ {
fixed (IntPtr* actors = _debugDrawData.ActorsPtrs) fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,6 +23,7 @@ namespace FlaxEditor.Windows
private bool _showGUI = true; private bool _showGUI = true;
private bool _showDebugDraw = false; private bool _showDebugDraw = false;
private bool _isMaximized = false, _isUnlockingMouse = false; private bool _isMaximized = false, _isUnlockingMouse = false;
private bool _isFloating = false, _isBorderless = false;
private bool _cursorVisible = true; private bool _cursorVisible = true;
private float _gameStartTime; private float _gameStartTime;
private GUI.Docking.DockState _maximizeRestoreDockState; private GUI.Docking.DockState _maximizeRestoreDockState;
@@ -68,7 +69,7 @@ namespace FlaxEditor.Windows
} }
/// <summary> /// <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> /// </summary>
private bool IsMaximized private bool IsMaximized
{ {
@@ -78,20 +79,42 @@ namespace FlaxEditor.Windows
if (_isMaximized == value) if (_isMaximized == value)
return; return;
_isMaximized = value; _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; var rootWindow = RootWindow;
if (value) if (value)
{ {
// Maximize
_maximizeRestoreDockTo = _dockedTo; _maximizeRestoreDockTo = _dockedTo;
_maximizeRestoreDockState = _dockedTo.TryGetDockState(out _); _maximizeRestoreDockState = _dockedTo.TryGetDockState(out _);
if (_maximizeRestoreDockState != GUI.Docking.DockState.Float) if (_maximizeRestoreDockState != GUI.Docking.DockState.Float)
{ {
var monitorBounds = Platform.GetMonitorBounds(PointToScreen(Size * 0.5f)); var monitorBounds = Platform.GetMonitorBounds(PointToScreen(Size * 0.5f));
ShowFloating(monitorBounds.Location + new Float2(200, 200), Float2.Zero, WindowStartPosition.Manual); var size = DefaultSize;
rootWindow = RootWindow; var location = monitorBounds.Location + monitorBounds.Size * 0.5f - size * 0.5f;
ShowFloating(location, size, WindowStartPosition.Manual);
} }
if (rootWindow != null && !rootWindow.IsMaximized)
rootWindow.Maximize();
} }
else 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> /// <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. /// 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> /// </summary>
@@ -318,6 +368,20 @@ namespace FlaxEditor.Windows
InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty)); InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty));
InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay); InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay);
InputActions.Add(options => options.ToggleFullscreen, () => { if (Editor.IsPlayMode) IsMaximized = !IsMaximized; }); 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) private void ChangeViewportRatio(ViewportScaleOptions v)
@@ -474,7 +538,9 @@ namespace FlaxEditor.Windows
/// <inheritdoc /> /// <inheritdoc />
public override void OnPlayEnd() public override void OnPlayEnd()
{ {
IsFloating = false;
IsMaximized = false; IsMaximized = false;
IsBorderless = false;
Cursor = CursorType.Default; Cursor = CursorType.Default;
} }
@@ -856,7 +922,7 @@ namespace FlaxEditor.Windows
var selection = Editor.SceneEditing.Selection; var selection = Editor.SceneEditing.Selection;
for (var i = 0; i < selection.Count; i++) 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) if (!drawAnySelectedControl)
{ {
@@ -902,7 +968,7 @@ namespace FlaxEditor.Windows
if (animTime < 0) if (animTime < 0)
{ {
float alpha = Mathf.Saturate(-animTime / fadeOutTime); 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 // Add overlay during debugger breakpoint hang
@@ -956,6 +1022,29 @@ namespace FlaxEditor.Windows
Focus(); 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> /// <summary>
/// Takes the screenshot of the current viewport. /// Takes the screenshot of the current viewport.
/// </summary> /// </summary>

View File

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

View File

@@ -11,7 +11,7 @@
/// <summary> /// <summary>
/// Behavior instance script that runs Behavior Tree execution. /// Behavior instance script that runs Behavior Tree execution.
/// </summary> /// </summary>
API_CLASS() class FLAXENGINE_API Behavior : public Script API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script
{ {
API_AUTO_SERIALIZATION(); API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(Behavior); 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); 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 BehaviorTreeHasTagDecorator::CanUpdate(const BehaviorUpdateContext& context)
{ {
bool result = false; bool result = false;

View File

@@ -445,6 +445,27 @@ public:
bool CanUpdate(const BehaviorUpdateContext& context) override; 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> /// <summary>
/// Checks if certain actor (from knowledge) has a given tag assigned. /// Checks if certain actor (from knowledge) has a given tag assigned.
/// </summary> /// </summary>

View File

@@ -900,5 +900,6 @@ private:
Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state); Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state);
void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr); 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, 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); 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) 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; int32 transitionIndex = 0;
const AnimGraphNode::StateBaseData& stateData = state->Data.State;
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex) while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
{ {
const uint16 idx = stateData.Transitions[transitionIndex]; 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) 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) if (transition)
{ {
InitStateTransition(context, stateMachineBucket, transition); InitStateTransition(context, stateMachineBucket, transition);

View File

@@ -281,10 +281,26 @@ Asset::LoadResult SceneAnimation::load()
track.TrackStateIndex = TrackStatesCount++; track.TrackStateIndex = TrackStatesCount++;
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1); trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 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->ValueSize = trackData->ValueSize;
trackRuntime->KeyframesCount = trackData->KeyframesCount; trackRuntime->KeyframesCount = trackData->KeyframesCount;
trackRuntime->Keyframes = stream.Move(keyframesDataSize); trackRuntime->Keyframes = stream.Move(keyframesDataSize);
trackRuntime->KeyframesSize = keyframesDataSize;
needsParent = true; needsParent = true;
break; break;
} }
@@ -298,6 +314,7 @@ Asset::LoadResult SceneAnimation::load()
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1); trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1); trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3); const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3);
ASSERT(trackData->ValueSize > 0);
trackRuntime->ValueSize = trackData->ValueSize; trackRuntime->ValueSize = trackData->ValueSize;
trackRuntime->KeyframesCount = trackData->KeyframesCount; trackRuntime->KeyframesCount = trackData->KeyframesCount;
trackRuntime->Keyframes = stream.Move(keyframesDataSize); trackRuntime->Keyframes = stream.Move(keyframesDataSize);
@@ -375,6 +392,7 @@ Asset::LoadResult SceneAnimation::load()
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1); trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1); trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
trackRuntime->ValueSize = trackData->ValueSize; trackRuntime->ValueSize = trackData->ValueSize;
ASSERT(trackData->ValueSize > 0);
trackRuntime->KeyframesCount = trackData->KeyframesCount; trackRuntime->KeyframesCount = trackData->KeyframesCount;
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime)); const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackData->KeyframesCount); 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). /// 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> /// </summary>
void* Keyframes; void* Keyframes;
int32 KeyframesSize;
}; };
}; };

View File

@@ -7,6 +7,7 @@
#include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Level/Actors/Camera.h" #include "Engine/Level/Actors/Camera.h"
#include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Audio/AudioClip.h" #include "Engine/Audio/AudioClip.h"
#include "Engine/Audio/AudioSource.h" #include "Engine/Audio/AudioSource.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
@@ -19,6 +20,7 @@
#include "Engine/Scripting/ManagedCLR/MField.h" #include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h" #include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/Internal/ManagedSerialization.h"
// This could be Update, LateUpdate or FixedUpdate // This could be Update, LateUpdate or FixedUpdate
#define UPDATE_POINT Update #define UPDATE_POINT Update
@@ -370,47 +372,96 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
case SceneAnimation::Track::Types::KeyframesProperty: case SceneAnimation::Track::Types::KeyframesProperty:
case SceneAnimation::Track::Types::ObjectReferenceProperty: case SceneAnimation::Track::Types::ObjectReferenceProperty:
{ {
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>(); const auto trackRuntime = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
const int32 count = trackDataKeyframes->KeyframesCount; const int32 count = trackRuntime->KeyframesCount;
if (count == 0) if (count == 0)
return false; return false;
// Find the keyframe at time // 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
int32 keyframeSize = sizeof(float) + trackDataKeyframes->ValueSize; if (trackRuntime->ValueSize != 0)
#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)
{ {
const int32 half = searchLength >> 1; // Find the keyframe at time (binary search)
int32 mid = start + half; int32 keyframeSize = sizeof(float) + trackRuntime->ValueSize;
if (keyTime < GET_KEY_TIME(mid)) #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 else
{ {
start = mid + 1; // POD memory
searchLength -= half + 1; 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 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; break;
} }
@@ -479,13 +530,13 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
} }
case SceneAnimation::Track::Types::StringProperty: case SceneAnimation::Track::Types::StringProperty:
{ {
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>(); const auto trackRuntime = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
const int32 count = trackDataKeyframes->KeyframesCount; const int32 count = trackRuntime->KeyframesCount;
if (count == 0) if (count == 0)
return false; return false;
const auto keyframesTimes = (float*)((byte*)trackDataKeyframes + sizeof(SceneAnimation::StringPropertyTrack::Runtime)); const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackDataKeyframes->KeyframesCount); const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackRuntime->KeyframesCount);
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackDataKeyframes->KeyframesCount); const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackRuntime->KeyframesCount);
// Find the keyframe at time // Find the keyframe at time
#define GET_KEY_TIME(idx) keyframesTimes[idx] #define GET_KEY_TIME(idx) keyframesTimes[idx]
@@ -522,7 +573,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
auto& childTrack = anim->Tracks[childTrackIndex]; auto& childTrack = anim->Tracks[childTrackIndex];
if (childTrack.Disabled || childTrack.ParentIndex != trackIndex) if (childTrack.Disabled || childTrack.ParentIndex != trackIndex)
continue; continue;
const auto childTrackRuntimeData = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>(); const auto childTrackRuntime = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
auto& childTrackState = _tracks[stateIndexOffset + childTrack.TrackStateIndex]; auto& childTrackState = _tracks[stateIndexOffset + childTrack.TrackStateIndex];
// Cache field // Cache field
@@ -532,7 +583,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
if (!type) if (!type)
continue; continue;
MClass* mclass = MCore::Type::GetClass(type); MClass* mclass = MCore::Type::GetClass(type);
childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName); childTrackState.Field = mclass->GetField(childTrackRuntime->PropertyName);
if (!childTrackState.Field) if (!childTrackState.Field)
continue; continue;
} }
@@ -956,7 +1007,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value)) if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
{ {
// Set the 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; value = (void*)*(intptr*)value;
if (state.Property) if (state.Property)
{ {

View File

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

View File

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

View File

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

View File

@@ -943,6 +943,11 @@ void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, c
DrawLine(origin, origin + direction, color, duration, depthTest); 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) void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float length, float duration, bool depthTest)
{ {
if (isnan(length) || isinf(length)) 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> /// <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); 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> /// <summary>
/// Draws the line in a direction. /// Draws the line in a direction.
/// </summary> /// </summary>

View File

@@ -7,7 +7,7 @@ namespace FlaxEngine
/// <summary> /// <summary>
/// Virtual input axis binding. Helps with listening for a selected axis input. /// Virtual input axis binding. Helps with listening for a selected axis input.
/// </summary> /// </summary>
public class InputAxis public class InputAxis : IComparable, IComparable<InputAxis>
{ {
/// <summary> /// <summary>
/// The name of the axis to use. See <see cref="Input.AxisMappings"/>. /// The name of the axis to use. See <see cref="Input.AxisMappings"/>.
@@ -47,13 +47,13 @@ namespace FlaxEngine
Input.AxisValueChanged += Handler; Input.AxisValueChanged += Handler;
Name = name; Name = name;
} }
private void Handler(string name) private void Handler(string name)
{ {
if (string.Equals(Name, name, StringComparison.OrdinalIgnoreCase)) if (string.Equals(Name, name, StringComparison.OrdinalIgnoreCase))
ValueChanged?.Invoke(); ValueChanged?.Invoke();
} }
/// <summary> /// <summary>
/// Finalizes an instance of the <see cref="InputAxis"/> class. /// Finalizes an instance of the <see cref="InputAxis"/> class.
/// </summary> /// </summary>
@@ -61,7 +61,7 @@ namespace FlaxEngine
{ {
Input.AxisValueChanged -= Handler; Input.AxisValueChanged -= Handler;
} }
/// <summary> /// <summary>
/// Releases this object. /// Releases this object.
/// </summary> /// </summary>
@@ -70,5 +70,35 @@ namespace FlaxEngine
Input.AxisValueChanged -= Handler; Input.AxisValueChanged -= Handler;
GC.SuppressFinalize(this); 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> /// <summary>
/// Virtual input action binding. Helps with listening for a selected input event. /// Virtual input action binding. Helps with listening for a selected input event.
/// </summary> /// </summary>
public class InputEvent public class InputEvent : IComparable, IComparable<InputEvent>
{ {
/// <summary> /// <summary>
/// The name of the action to use. See <see cref="Input.ActionMappings"/>. /// The name of the action to use. See <see cref="Input.ActionMappings"/>.
@@ -21,7 +21,7 @@ namespace FlaxEngine
public bool Active => Input.GetAction(Name); public bool Active => Input.GetAction(Name);
/// <summary> /// <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> /// </summary>
public InputActionState State => Input.GetActionState(Name); 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. /// Occurs when event is pressed (e.g. user pressed a key). Called before scripts update.
/// </summary> /// </summary>
public event Action Pressed; public event Action Pressed;
/// <summary> /// <summary>
/// Occurs when event is being pressing (e.g. user pressing a key). Called before scripts update. /// Occurs when event is being pressing (e.g. user pressing a key). Called before scripts update.
/// </summary> /// </summary>
public event Action Pressing; public event Action Pressing;
/// <summary> /// <summary>
/// Occurs when event is released (e.g. user releases a key). Called before scripts update. /// Occurs when event is released (e.g. user releases a key). Called before scripts update.
/// </summary> /// </summary>
@@ -102,5 +102,35 @@ namespace FlaxEngine
Input.ActionTriggered -= Handler; Input.ActionTriggered -= Handler;
GC.SuppressFinalize(this); 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 #endif
private static Dictionary<object, ManagedHandle> classAttributesCacheCollectible = new(); private static Dictionary<object, ManagedHandle> classAttributesCacheCollectible = new();
private static Dictionary<Assembly, ManagedHandle> assemblyHandles = 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(); private static Dictionary<string, IntPtr> loadedNativeLibraries = new();
internal static Dictionary<string, string> libraryPaths = 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 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. // 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; uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index]; ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
if (alloc.size < size) if (alloc.size < size)
@@ -1727,25 +1727,36 @@ namespace FlaxEngine.Interop
return tuple; 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 try
{ {
var marshalType = type; var marshalType = type;
if (type.IsEnum) if (type.IsEnum)
marshalType = type.GetEnumUnderlyingType(); marshalType = type.GetEnumUnderlyingType();
size = Marshal.SizeOf(marshalType); MarshalSize = Marshal.SizeOf(marshalType);
} }
catch catch
{ {
// Workaround the issue where structure defined within generic type instance (eg. MyType<int>.MyStruct) fails to get size // 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 // https://github.com/dotnet/runtime/issues/46426
var obj = Activator.CreateInstance(type); var obj = RuntimeHelpers.GetUninitializedObject(type);
size = Marshal.SizeOf(obj); 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; return size;
} }

View File

@@ -1764,14 +1764,6 @@ bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeM
} }
Scripting::ObjectsLookupIdMapping.Set(nullptr); 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 // Update objects order
//for (int32 i = 0; i < objectsCount; i++) //for (int32 i = 0; i < objectsCount; i++)
{ {

View File

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

View File

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

View File

@@ -325,7 +325,7 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
dummyScript->Data = MoveTemp(bufferStr); dummyScript->Data = MoveTemp(bufferStr);
} }
#endif #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 #endif

View File

@@ -3,6 +3,7 @@
#include "SceneQuery.h" #include "SceneQuery.h"
#include "Engine/Scripting/Script.h" #include "Engine/Scripting/Script.h"
#include "Engine/Profiler/Profiler.h" #include "Engine/Profiler/Profiler.h"
#include "Scripts/MissingScript.h"
Actor* SceneQuery::RaycastScene(const Ray& ray) Actor* SceneQuery::RaycastScene(const Ray& ray)
{ {
@@ -51,6 +52,15 @@ bool GetAllSerializableSceneObjectsQuery(Actor* actor, Array<SceneObject*>& obje
return false; return false;
objects.Add(actor); objects.Add(actor);
objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count()); 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; return true;
} }

View File

@@ -11,7 +11,9 @@ int main(int argc, char* argv[])
StringBuilder args; StringBuilder args;
for (int i = 1; i < argc; i++) 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) if (i + 1 != argc)
args.Append(TEXT(' ')); args.Append(TEXT(' '));

View File

@@ -46,6 +46,7 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe
{ {
if (!_crowd || !navMesh) if (!_crowd || !navMesh)
return true; 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 // 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) if (navMesh->GetNavMesh() == nullptr)
@@ -145,6 +146,7 @@ void NavCrowd::ResetAgentMove(int32 id)
void NavCrowd::RemoveAgent(int32 id) void NavCrowd::RemoveAgent(int32 id)
{ {
CHECK(id >= 0 && id < _crowd->getAgentCount());
_crowd->removeAgent(id); _crowd->removeAgent(id);
} }

View File

@@ -10,7 +10,7 @@
/// Actor script component that synchronizes the Transform over the network. /// Actor script component that synchronizes the Transform over the network.
/// </summary> /// </summary>
/// <remarks>Interpolation and prediction logic based on https://www.gabrielgambetta.com/client-server-game-architecture.html.</remarks> /// <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(); API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(NetworkTransform); DECLARE_SCRIPTING_TYPE(NetworkTransform);

View File

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

View File

@@ -366,7 +366,13 @@ void PlatformBase::Error(const Char* msg)
#if PLATFORM_HAS_HEADLESS_MODE #if PLATFORM_HAS_HEADLESS_MODE
if (CommandLine::Options.Headless) 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; std::cout << "Error: " << msg << std::endl;
#endif
} }
else else
#endif #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) 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 StringAsUTF8<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length());
const StringAsANSI<> titleAnsi(*title, title.Length()); const StringAsUTF8<> titleAnsi(*title, title.Length());
const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : "."; const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : ".";
String xdgCurrentDesktop; String xdgCurrentDesktop;
StringBuilder fileFilter; 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) 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; String xdgCurrentDesktop;
Platform::GetEnvironmentVariable(TEXT("XDG_CURRENT_DESKTOP"), 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) bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
{ {
const StringAsANSI<> pathAnsi(*path, path.Length()); const StringAsUTF8<> pathAnsi(*path, path.Length());
char cmd[2048]; char cmd[2048];
sprintf(cmd, "xdg-open %s &", pathAnsi.Get()); sprintf(cmd, "xdg-open %s &", pathAnsi.Get());
system(cmd); system(cmd);
@@ -167,7 +167,7 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
bool LinuxFileSystem::CreateDirectory(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 // Skip if already exists
struct stat fileInfo; struct stat fileInfo;
@@ -258,7 +258,7 @@ bool DeleteUnixPathTree(const char* path)
bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents) bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
{ {
const StringAsANSI<> pathANSI(*path, path.Length()); const StringAsUTF8<> pathANSI(*path, path.Length());
if (deleteContents) if (deleteContents)
{ {
return DeleteUnixPathTree(pathANSI.Get()); return DeleteUnixPathTree(pathANSI.Get());
@@ -272,7 +272,7 @@ bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
bool LinuxFileSystem::DirectoryExists(const StringView& path) bool LinuxFileSystem::DirectoryExists(const StringView& path)
{ {
struct stat fileInfo; struct stat fileInfo;
const StringAsANSI<> pathANSI(*path, path.Length()); const StringAsUTF8<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1) if (stat(pathANSI.Get(), &fileInfo) != -1)
{ {
return S_ISDIR(fileInfo.st_mode); 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) bool LinuxFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
{ {
const StringAsANSI<> pathANSI(*path, path.Length()); const StringAsUTF8<> pathANSI(*path, path.Length());
const StringAsANSI<> searchPatternANSI(searchPattern); const StringAsUTF8<> searchPatternANSI(searchPattern);
// Check if use only top directory // Check if use only top directory
if (option == DirectorySearchOption::TopDirectoryOnly) if (option == DirectorySearchOption::TopDirectoryOnly)
@@ -297,7 +297,7 @@ bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String&
DIR* dir; DIR* dir;
struct stat statPath, statEntry; struct stat statPath, statEntry;
struct dirent* entry; struct dirent* entry;
const StringAsANSI<> pathANSI(*directory, directory.Length()); const StringAsUTF8<> pathANSI(*directory, directory.Length());
const char* path = pathANSI.Get(); const char* path = pathANSI.Get();
// Stat for the path // Stat for the path
@@ -353,7 +353,7 @@ bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String&
bool LinuxFileSystem::FileExists(const StringView& path) bool LinuxFileSystem::FileExists(const StringView& path)
{ {
struct stat fileInfo; struct stat fileInfo;
const StringAsANSI<> pathANSI(*path, path.Length()); const StringAsUTF8<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1) if (stat(pathANSI.Get(), &fileInfo) != -1)
{ {
return S_ISREG(fileInfo.st_mode); return S_ISREG(fileInfo.st_mode);
@@ -363,7 +363,7 @@ bool LinuxFileSystem::FileExists(const StringView& path)
bool LinuxFileSystem::DeleteFile(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; return unlink(pathANSI.Get()) != 0;
} }
@@ -371,7 +371,7 @@ uint64 LinuxFileSystem::GetFileSize(const StringView& path)
{ {
struct stat fileInfo; struct stat fileInfo;
fileInfo.st_size = 0; fileInfo.st_size = 0;
const StringAsANSI<> pathANSI(*path, path.Length()); const StringAsUTF8<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) != -1) if (stat(pathANSI.Get(), &fileInfo) != -1)
{ {
// Check for directories // Check for directories
@@ -385,7 +385,7 @@ uint64 LinuxFileSystem::GetFileSize(const StringView& path)
bool LinuxFileSystem::IsReadOnly(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) if (access(pathANSI.Get(), W_OK) == -1)
{ {
return errno == EACCES; return errno == EACCES;
@@ -395,7 +395,7 @@ bool LinuxFileSystem::IsReadOnly(const StringView& path)
bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly) bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
{ {
const StringAsANSI<> pathANSI(*path, path.Length()); const StringAsUTF8<> pathANSI(*path, path.Length());
struct stat fileInfo; struct stat fileInfo;
if (stat(pathANSI.Get(), &fileInfo) != -1) if (stat(pathANSI.Get(), &fileInfo) != -1)
{ {
@@ -422,15 +422,15 @@ bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, boo
if (overwrite) 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 (errno == EXDEV)
{ {
if (!CopyFile(dst, src)) if (!CopyFile(dst, src))
{ {
unlink(StringAsANSI<>(*src, src.Length()).Get()); unlink(StringAsUTF8<>(*src, src.Length()).Get());
return false; 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) bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src)
{ {
const StringAsANSI<> srcANSI(*src, src.Length()); const StringAsUTF8<> srcANSI(*src, src.Length());
const StringAsANSI<> dstANSI(*dst, dst.Length()); const StringAsUTF8<> dstANSI(*dst, dst.Length());
int srcFile, dstFile; int srcFile, dstFile;
char buffer[4096]; char buffer[4096];
@@ -752,7 +752,7 @@ bool LinuxFileSystem::getFilesFromDirectoryAll(Array<String>& results, const cha
DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path) DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
{ {
struct stat fileInfo; struct stat fileInfo;
const StringAsANSI<> pathANSI(*path, path.Length()); const StringAsUTF8<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) == -1) if (stat(pathANSI.Get(), &fileInfo) == -1)
{ {
return DateTime::MinValue(); return DateTime::MinValue();

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