Merge remote-tracking branch 'origin/master' into 1.8
This commit is contained in:
@@ -161,7 +161,7 @@ namespace FlaxEditor.Content.GUI
|
||||
{
|
||||
var style = Style.Current;
|
||||
var rect = new Rectangle(Float2.Zero, Size);
|
||||
var color = IsDragOver ? style.BackgroundSelected * 0.6f : (_mouseDown ? style.BackgroundSelected : (IsMouseOver ? style.BackgroundHighlighted : Color.Transparent));
|
||||
var color = IsDragOver ? Color.Transparent : (_mouseDown ? style.BackgroundSelected : (IsMouseOver ? style.BackgroundHighlighted : Color.Transparent));
|
||||
Render2D.FillRectangle(rect, color);
|
||||
Render2D.DrawSprite(Editor.Instance.Icons.ArrowRight12, new Rectangle(rect.Location.X, rect.Y + rect.Size.Y * 0.25f, rect.Size.X, rect.Size.X), EnabledInHierarchy ? style.Foreground : style.ForegroundDisabled);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Windows;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -52,14 +54,17 @@ namespace FlaxEditor.Content.GUI
|
||||
public partial class ContentView : ContainerControl, IContentItemOwner
|
||||
{
|
||||
private readonly List<ContentItem> _items = new List<ContentItem>(256);
|
||||
private readonly List<ContentItem> _selection = new List<ContentItem>(16);
|
||||
private readonly List<ContentItem> _selection = new List<ContentItem>();
|
||||
|
||||
private float _viewScale = 1.0f;
|
||||
private ContentViewType _viewType = ContentViewType.Tiles;
|
||||
private bool _isRubberBandSpanning = false;
|
||||
private Float2 _mousePresslocation;
|
||||
private bool _isRubberBandSpanning;
|
||||
private Float2 _mousePressLocation;
|
||||
private Rectangle _rubberBandRectangle;
|
||||
|
||||
private bool _validDragOver;
|
||||
private DragActors _dragActors;
|
||||
|
||||
#region External Events
|
||||
|
||||
/// <summary>
|
||||
@@ -193,6 +198,7 @@ namespace FlaxEditor.Content.GUI
|
||||
OnDelete?.Invoke(_selection);
|
||||
}),
|
||||
new InputActionsContainer.Binding(options => options.SelectAll, SelectAll),
|
||||
new InputActionsContainer.Binding(options => options.DeselectAll, DeselectAll),
|
||||
new InputActionsContainer.Binding(options => options.Rename, () =>
|
||||
{
|
||||
if (HasSelection && _selection[0].CanRename)
|
||||
@@ -397,10 +403,7 @@ namespace FlaxEditor.Content.GUI
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all the items.
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
private void BulkSelectUpdate(bool select = true)
|
||||
{
|
||||
// Lock layout
|
||||
var wasLayoutLocked = IsLayoutLocked;
|
||||
@@ -408,13 +411,30 @@ namespace FlaxEditor.Content.GUI
|
||||
|
||||
// Select items
|
||||
_selection.Clear();
|
||||
_selection.AddRange(_items);
|
||||
if (select)
|
||||
_selection.AddRange(_items);
|
||||
|
||||
// Unload and perform UI layout
|
||||
IsLayoutLocked = wasLayoutLocked;
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all the items.
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
BulkSelectUpdate(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselects all the items.
|
||||
/// </summary>
|
||||
public void DeselectAll()
|
||||
{
|
||||
BulkSelectUpdate(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselects the specified item.
|
||||
/// </summary>
|
||||
@@ -595,7 +615,9 @@ namespace FlaxEditor.Content.GUI
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _validDragOver)
|
||||
{
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f);
|
||||
var bounds = new Rectangle(Float2.One, Size - Float2.One * 2);
|
||||
Render2D.FillRectangle(bounds, style.Selection);
|
||||
Render2D.DrawRectangle(bounds, style.SelectionBorder);
|
||||
}
|
||||
|
||||
// Check if it's an empty thing
|
||||
@@ -604,10 +626,11 @@ namespace FlaxEditor.Content.GUI
|
||||
Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
|
||||
}
|
||||
|
||||
// Selection
|
||||
if (_isRubberBandSpanning)
|
||||
{
|
||||
Render2D.FillRectangle(_rubberBandRectangle, Color.Orange * 0.4f);
|
||||
Render2D.DrawRectangle(_rubberBandRectangle, Color.Orange);
|
||||
Render2D.FillRectangle(_rubberBandRectangle, style.Selection);
|
||||
Render2D.DrawRectangle(_rubberBandRectangle, style.SelectionBorder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,8 +642,8 @@ namespace FlaxEditor.Content.GUI
|
||||
|
||||
if (button == MouseButton.Left)
|
||||
{
|
||||
_mousePresslocation = location;
|
||||
_rubberBandRectangle = new Rectangle(_mousePresslocation, 0, 0);
|
||||
_mousePressLocation = location;
|
||||
_rubberBandRectangle = new Rectangle(_mousePressLocation, 0, 0);
|
||||
_isRubberBandSpanning = true;
|
||||
StartMouseCapture();
|
||||
}
|
||||
@@ -632,8 +655,8 @@ namespace FlaxEditor.Content.GUI
|
||||
{
|
||||
if (_isRubberBandSpanning)
|
||||
{
|
||||
_rubberBandRectangle.Width = location.X - _mousePresslocation.X;
|
||||
_rubberBandRectangle.Height = location.Y - _mousePresslocation.Y;
|
||||
_rubberBandRectangle.Width = location.X - _mousePressLocation.X;
|
||||
_rubberBandRectangle.Height = location.Y - _mousePressLocation.Y;
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
@@ -768,6 +791,114 @@ namespace FlaxEditor.Content.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragEnter(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
// Check if drop file(s)
|
||||
if (data is DragDataFiles)
|
||||
{
|
||||
_validDragOver = true;
|
||||
return DragDropEffect.Copy;
|
||||
}
|
||||
|
||||
// Check if drop actor(s)
|
||||
if (_dragActors == null)
|
||||
_dragActors = new DragActors(ValidateDragActors);
|
||||
if (_dragActors.OnDragEnter(data))
|
||||
{
|
||||
_validDragOver = true;
|
||||
return DragDropEffect.Move;
|
||||
}
|
||||
|
||||
return DragDropEffect.None;
|
||||
}
|
||||
|
||||
private bool ValidateDragActors(ActorNode actor)
|
||||
{
|
||||
return actor.CanCreatePrefab && Editor.Instance.Windows.ContentWin.CurrentViewFolder.CanHaveAssets;
|
||||
}
|
||||
|
||||
private void ImportActors(DragActors actors, ContentFolder location)
|
||||
{
|
||||
foreach (var actorNode in actors.Objects)
|
||||
{
|
||||
var actor = actorNode.Actor;
|
||||
if (actors.Objects.Contains(actorNode.ParentNode as ActorNode))
|
||||
continue;
|
||||
|
||||
Editor.Instance.Prefabs.CreatePrefab(actor, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||
{
|
||||
_validDragOver = false;
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
if (data is DragDataFiles)
|
||||
{
|
||||
_validDragOver = true;
|
||||
result = DragDropEffect.Copy;
|
||||
}
|
||||
else if (_dragActors != null && _dragActors.HasValidDrag)
|
||||
{
|
||||
_validDragOver = true;
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override DragDropEffect OnDragDrop(ref Float2 location, DragData data)
|
||||
{
|
||||
var result = base.OnDragDrop(ref location, data);
|
||||
if (result != DragDropEffect.None)
|
||||
return result;
|
||||
|
||||
// Check if drop file(s)
|
||||
if (data is DragDataFiles files)
|
||||
{
|
||||
// Import files
|
||||
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
|
||||
if (currentFolder != null)
|
||||
Editor.Instance.ContentImporting.Import(files.Files, currentFolder);
|
||||
result = DragDropEffect.Copy;
|
||||
}
|
||||
// Check if drop actor(s)
|
||||
else if (_dragActors != null && _dragActors.HasValidDrag)
|
||||
{
|
||||
// Import actors
|
||||
var currentFolder = Editor.Instance.Windows.ContentWin.CurrentViewFolder;
|
||||
if (currentFolder != null)
|
||||
ImportActors(_dragActors, currentFolder);
|
||||
|
||||
_dragActors.OnDragDrop();
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
_validDragOver = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
_validDragOver = false;
|
||||
_dragActors?.OnDragLeave();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void PerformLayoutBeforeChildren()
|
||||
{
|
||||
@@ -833,6 +964,9 @@ namespace FlaxEditor.Content.GUI
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
|
||||
// Ensure to unlink all items
|
||||
ClearItems();
|
||||
|
||||
|
||||
@@ -241,7 +241,12 @@ namespace FlaxEditor.Content
|
||||
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _validDragOver)
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Style.Current.BackgroundSelected * 0.6f);
|
||||
{
|
||||
var style = Style.Current;
|
||||
var bounds = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, style.Selection);
|
||||
Render2D.DrawRectangle(bounds, style.SelectionBorder);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateDragItem(ContentItem item)
|
||||
|
||||
@@ -75,6 +75,8 @@ namespace FlaxEditor.Content
|
||||
/// <inheritdoc />
|
||||
public override void Create(string outputPath, object arg)
|
||||
{
|
||||
bool resetTransform = false;
|
||||
var transform = Transform.Identity;
|
||||
if (!(arg is Actor actor))
|
||||
{
|
||||
// Create default prefab root object
|
||||
@@ -86,8 +88,17 @@ namespace FlaxEditor.Content
|
||||
// Cleanup it after usage
|
||||
Object.Destroy(actor, 20.0f);
|
||||
}
|
||||
else if (actor.Scene != null)
|
||||
{
|
||||
// Create prefab with identity transform so the actor instance on a level will have it customized
|
||||
resetTransform = true;
|
||||
transform = actor.LocalTransform;
|
||||
actor.LocalTransform = Transform.Identity;
|
||||
}
|
||||
|
||||
PrefabManager.CreatePrefab(actor, outputPath, true);
|
||||
if (resetTransform)
|
||||
actor.LocalTransform = transform;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -747,7 +747,7 @@ namespace FlaxEditor.CustomEditors
|
||||
/// </summary>
|
||||
public void SetValueToDefault()
|
||||
{
|
||||
SetValueCloned(Values.DefaultValue);
|
||||
SetValue(Utilities.Utils.CloneValue(Values.DefaultValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -784,19 +784,7 @@ namespace FlaxEditor.CustomEditors
|
||||
return;
|
||||
}
|
||||
|
||||
SetValueCloned(Values.ReferenceValue);
|
||||
}
|
||||
|
||||
private void SetValueCloned(object value)
|
||||
{
|
||||
// For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor
|
||||
if (value != null && !value.GetType().IsValueType)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value);
|
||||
value = JsonSerializer.Deserialize(json, value.GetType());
|
||||
}
|
||||
|
||||
SetValue(value);
|
||||
SetValue(Utilities.Utils.CloneValue(Values.ReferenceValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -245,9 +245,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
// Special case for new Script added to actor
|
||||
if (editor.Values[0] is Script script && !script.HasPrefabLink)
|
||||
{
|
||||
return CreateDiffNode(editor);
|
||||
}
|
||||
|
||||
// Skip if no change detected
|
||||
var isRefEdited = editor.Values.IsReferenceValueModified;
|
||||
@@ -258,9 +256,16 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
if (editor.ChildrenEditors.Count == 0 || (isRefEdited && editor is CollectionEditor))
|
||||
result = CreateDiffNode(editor);
|
||||
bool isScriptEditorWithRefValue = editor is ScriptsEditor && editor.Values.HasReferenceValue;
|
||||
bool isActorEditorInLevel = editor is ActorEditor && editor.Values[0] is Actor actor && actor.IsPrefabRoot && actor.Scene != null;
|
||||
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
|
||||
{
|
||||
var child = ProcessDiff(editor.ChildrenEditors[i], !isScriptEditorWithRefValue);
|
||||
var childEditor = editor.ChildrenEditors[i];
|
||||
|
||||
// Special case for root actor transformation (can be applied only in Prefab editor, not in Level)
|
||||
if (isActorEditorInLevel && childEditor.Values.Info.Name is "LocalPosition" or "LocalOrientation" or "LocalScale")
|
||||
continue;
|
||||
|
||||
var child = ProcessDiff(childEditor, !isScriptEditorWithRefValue);
|
||||
if (child != null)
|
||||
{
|
||||
if (result == null)
|
||||
@@ -276,7 +281,6 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
var prefabObjectScript = prefabObjectScripts[j];
|
||||
bool isRemoved = true;
|
||||
|
||||
for (int i = 0; i < editor.ChildrenEditors.Count; i++)
|
||||
{
|
||||
if (editor.ChildrenEditors[i].Values is ScriptsEditor.ScriptsContainer container && container.PrefabObjectId == prefabObjectScript.PrefabObjectID)
|
||||
@@ -286,14 +290,12 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRemoved)
|
||||
{
|
||||
var dummy = new RemovedScriptDummy
|
||||
{
|
||||
PrefabObject = prefabObjectScript
|
||||
};
|
||||
|
||||
var child = CreateDiffNode(dummy);
|
||||
if (result == null)
|
||||
result = CreateDiffNode(editor);
|
||||
|
||||
@@ -67,25 +67,6 @@ public class MissingScriptEditor : GenericEditor
|
||||
base.Initialize(layout);
|
||||
}
|
||||
|
||||
private void FindActorsWithMatchingMissingScript(List<MissingScript> missingScripts)
|
||||
{
|
||||
foreach (Actor actor in Level.GetActors(typeof(Actor)))
|
||||
{
|
||||
for (int scriptIndex = 0; scriptIndex < actor.ScriptsCount; scriptIndex++)
|
||||
{
|
||||
Script actorScript = actor.Scripts[scriptIndex];
|
||||
if (actorScript is not MissingScript missingActorScript)
|
||||
continue;
|
||||
|
||||
MissingScript currentMissing = Values[0] as MissingScript;
|
||||
if (missingActorScript.MissingTypeName != currentMissing.MissingTypeName)
|
||||
continue;
|
||||
|
||||
missingScripts.Add(missingActorScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RunReplacementMultiCast(List<IUndoAction> actions)
|
||||
{
|
||||
if (actions.Count == 0)
|
||||
@@ -104,16 +85,54 @@ public class MissingScriptEditor : GenericEditor
|
||||
}
|
||||
}
|
||||
|
||||
private void ReplaceScript(ScriptType script, bool replaceAllInScene)
|
||||
private void GetMissingScripts(Actor actor, string currentMissingTypeName, List<MissingScript> missingScripts)
|
||||
{
|
||||
var actions = new List<IUndoAction>(4);
|
||||
// Iterate over the scripts of this actor
|
||||
for (int i = 0; i < actor.ScriptsCount; i++)
|
||||
{
|
||||
if (actor.GetScript(i) is MissingScript otherMissing &&
|
||||
otherMissing.MissingTypeName == currentMissingTypeName)
|
||||
{
|
||||
missingScripts.Add(otherMissing);
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over this actor children (recursive)
|
||||
for (int i = 0; i < actor.ChildrenCount; i++)
|
||||
{
|
||||
GetMissingScripts(actor.GetChild(i), currentMissingTypeName, missingScripts);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReplaceScript(ScriptType script)
|
||||
{
|
||||
var missingScripts = new List<MissingScript>();
|
||||
if (!replaceAllInScene)
|
||||
missingScripts.Add((MissingScript)Values[0]);
|
||||
var currentMissing = (MissingScript)Values[0];
|
||||
var currentMissingTypeName = currentMissing.MissingTypeName;
|
||||
if (_shouldReplaceAllCheckbox.Checked)
|
||||
{
|
||||
if (currentMissing.Scene == null && currentMissing.Actor != null)
|
||||
{
|
||||
// Find all missing scripts in prefab instance
|
||||
GetMissingScripts(currentMissing.Actor.GetPrefabRoot(), currentMissingTypeName, missingScripts);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find all missing scripts in all loaded levels that match this type
|
||||
for (int i = 0; i < Level.ScenesCount; i++)
|
||||
{
|
||||
GetMissingScripts(Level.GetScene(i), currentMissingTypeName, missingScripts);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
FindActorsWithMatchingMissingScript(missingScripts);
|
||||
{
|
||||
// Use the current instance only
|
||||
foreach (var value in Values)
|
||||
missingScripts.Add((MissingScript)value);
|
||||
}
|
||||
|
||||
var actions = new List<IUndoAction>(4);
|
||||
foreach (var missingScript in missingScripts)
|
||||
actions.Add(AddRemoveScript.Add(missingScript.Actor, script));
|
||||
RunReplacementMultiCast(actions);
|
||||
@@ -149,7 +168,7 @@ public class MissingScriptEditor : GenericEditor
|
||||
var cm = new ItemsListContextMenu(180);
|
||||
for (int i = 0; i < scripts.Count; i++)
|
||||
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
|
||||
cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag, _shouldReplaceAllCheckbox.Checked);
|
||||
cm.ItemClicked += item => ReplaceScript((ScriptType)item.Tag);
|
||||
cm.SortItems();
|
||||
cm.Show(_dropPanel, _replaceScriptButton.BottomLeft - new Float2((cm.Width - _replaceScriptButton.Width) / 2, 0));
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var actions = new List<IUndoAction>();
|
||||
foreach (var body in bodies)
|
||||
{
|
||||
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(body.ID) });
|
||||
var action = new Actions.DeleteActorsAction(body);
|
||||
action.Do();
|
||||
actions.Add(action);
|
||||
}
|
||||
@@ -185,7 +185,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var body = bodies.FirstOrDefault(x => x.Name == name);
|
||||
if (body != null)
|
||||
{
|
||||
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(body.ID) });
|
||||
var action = new Actions.DeleteActorsAction(body);
|
||||
action.Do();
|
||||
Presenter.Undo?.AddAction(action);
|
||||
}
|
||||
@@ -224,7 +224,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
else
|
||||
{
|
||||
// Remove joint that will no longer be valid
|
||||
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(joint.ID) });
|
||||
var action = new Actions.DeleteActorsAction(joint);
|
||||
action.Do();
|
||||
Presenter.Undo?.AddAction(action);
|
||||
}
|
||||
@@ -233,7 +233,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
|
||||
// Remove body
|
||||
{
|
||||
var action = new Actions.DeleteActorsAction(new List<SceneGraphNode> { SceneGraphFactory.FindNode(body.ID) });
|
||||
var action = new Actions.DeleteActorsAction(body);
|
||||
action.Do();
|
||||
Presenter.Undo?.AddAction(action);
|
||||
}
|
||||
|
||||
@@ -151,8 +151,8 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
if (IsDragOver && _dragHandlers != null && _dragHandlers.HasValidDrag)
|
||||
{
|
||||
var area = new Rectangle(Float2.Zero, size);
|
||||
Render2D.FillRectangle(area, Color.Orange * 0.5f);
|
||||
Render2D.DrawRectangle(area, Color.Black);
|
||||
Render2D.FillRectangle(area, style.Selection);
|
||||
Render2D.DrawRectangle(area, style.SelectionBorder);
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
@@ -520,7 +520,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var color = FlaxEngine.GUI.Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f);
|
||||
var color = Style.Current.BackgroundSelected * (IsDragOver ? 0.9f : 0.1f);
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), color);
|
||||
}
|
||||
|
||||
|
||||
@@ -589,25 +589,27 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
LayoutElementsContainer yEl;
|
||||
LayoutElementsContainer hEl;
|
||||
LayoutElementsContainer vEl;
|
||||
Color axisColorX = ActorTransformEditor.AxisColorX;
|
||||
Color axisColorY = ActorTransformEditor.AxisColorY;
|
||||
if (xEq)
|
||||
{
|
||||
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values));
|
||||
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values));
|
||||
xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX);
|
||||
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX);
|
||||
}
|
||||
else
|
||||
{
|
||||
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values));
|
||||
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values));
|
||||
xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX);
|
||||
vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX);
|
||||
}
|
||||
if (yEq)
|
||||
{
|
||||
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values));
|
||||
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values));
|
||||
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY);
|
||||
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY);
|
||||
}
|
||||
else
|
||||
{
|
||||
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values));
|
||||
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values));
|
||||
yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY);
|
||||
hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY);
|
||||
}
|
||||
xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y);
|
||||
xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y);
|
||||
@@ -624,28 +626,34 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
|
||||
private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont)
|
||||
{
|
||||
var horUp = cont.VerticalPanel();
|
||||
horUp.Panel.Margin = Margin.Zero;
|
||||
return horUp;
|
||||
var panel = cont.VerticalPanel();
|
||||
panel.Panel.Margin = Margin.Zero;
|
||||
return panel;
|
||||
}
|
||||
|
||||
private CustomElementsContainer<UniformGridPanel> UniformGridTwoByOne(LayoutElementsContainer cont)
|
||||
{
|
||||
var horUp = cont.CustomContainer<UniformGridPanel>();
|
||||
horUp.CustomControl.SlotsHorizontally = 2;
|
||||
horUp.CustomControl.SlotsVertically = 1;
|
||||
horUp.CustomControl.SlotPadding = Margin.Zero;
|
||||
horUp.CustomControl.ClipChildren = false;
|
||||
return horUp;
|
||||
var grid = cont.CustomContainer<UniformGridPanel>();
|
||||
grid.CustomControl.SlotsHorizontally = 2;
|
||||
grid.CustomControl.SlotsVertically = 1;
|
||||
grid.CustomControl.SlotPadding = Margin.Zero;
|
||||
grid.CustomControl.ClipChildren = false;
|
||||
return grid;
|
||||
}
|
||||
|
||||
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values)
|
||||
private CustomElementsContainer<UniformGridPanel> UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor)
|
||||
{
|
||||
CustomElementsContainer<UniformGridPanel> hor = UniformGridTwoByOne(el);
|
||||
hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0);
|
||||
LabelElement lab = hor.Label(text);
|
||||
hor.Object(values);
|
||||
return hor;
|
||||
var grid = UniformGridTwoByOne(el);
|
||||
grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1);
|
||||
var label = grid.Label(text);
|
||||
var editor = grid.Object(values);
|
||||
if (editor is FloatEditor floatEditor && floatEditor.Element is FloatValueElement floatEditorElement)
|
||||
{
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
floatEditorElement.ValueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor);
|
||||
floatEditorElement.ValueBox.BorderSelectedColor = borderColor;
|
||||
}
|
||||
return grid;
|
||||
}
|
||||
|
||||
private bool _cachedXEq;
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// </summary>
|
||||
public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// The axes colors grey out scale when input field is not focused.
|
||||
/// </summary>
|
||||
public static float AxisGreyOutFactor = 0.6f;
|
||||
|
||||
/// <summary>
|
||||
/// Custom editor for actor position property.
|
||||
/// </summary>
|
||||
@@ -39,12 +44,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
// Override colors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
var grayOutFactor = 0.6f;
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
|
||||
XElement.ValueBox.BorderSelectedColor = AxisColorX;
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
|
||||
YElement.ValueBox.BorderSelectedColor = AxisColorY;
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
|
||||
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
|
||||
}
|
||||
}
|
||||
@@ -62,12 +66,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
// Override colors
|
||||
var back = FlaxEngine.GUI.Style.Current.TextBoxBackground;
|
||||
var grayOutFactor = 0.6f;
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor);
|
||||
XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor);
|
||||
XElement.ValueBox.BorderSelectedColor = AxisColorX;
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor);
|
||||
YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor);
|
||||
YElement.ValueBox.BorderSelectedColor = AxisColorY;
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor);
|
||||
ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor);
|
||||
ZElement.ValueBox.BorderSelectedColor = AxisColorZ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
@@ -47,8 +46,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
if (elementType.IsValueType || NotNullItems)
|
||||
{
|
||||
// Fill new entries with the last value
|
||||
var lastValue = array.GetValue(oldSize - 1);
|
||||
for (int i = oldSize; i < newSize; i++)
|
||||
Array.Copy(array, oldSize - 1, newValues, i, 1);
|
||||
newValues.SetValue(Utilities.Utils.CloneValue(lastValue), i);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -542,9 +542,10 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
{
|
||||
if (_dragHandlers is { HasValidDrag: true })
|
||||
{
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
var area = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(area, Color.Orange * 0.5f);
|
||||
Render2D.DrawRectangle(area, Color.Black);
|
||||
Render2D.FillRectangle(area, style.Selection);
|
||||
Render2D.DrawRectangle(area, style.SelectionBorder);
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
|
||||
@@ -220,7 +220,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _hasValidDragOver)
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f);
|
||||
{
|
||||
var bounds = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, style.Selection);
|
||||
Render2D.DrawRectangle(bounds, style.SelectionBorder);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -175,7 +175,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _hasValidDragOver)
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f);
|
||||
{
|
||||
var bounds = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, style.Selection);
|
||||
Render2D.DrawRectangle(bounds, style.SelectionBorder);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1676,6 +1676,9 @@ namespace FlaxEditor
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool Internal_CanSetToRoot(IntPtr prefab, IntPtr newRoot);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetPrefabNestedObject", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
internal static partial void Internal_GetPrefabNestedObject(IntPtr prefabId, IntPtr prefabObjectId, IntPtr outPrefabId, IntPtr outPrefabObjectId);
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_GetAnimationTime", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))]
|
||||
internal static partial float Internal_GetAnimationTime(IntPtr animatedModel);
|
||||
|
||||
|
||||
@@ -182,7 +182,11 @@ namespace FlaxEditor.GUI
|
||||
|
||||
// Check if drag is over
|
||||
if (IsDragOver && _dragOverElement != null && _dragOverElement.HasValidDrag)
|
||||
Render2D.FillRectangle(iconRect, style.BackgroundSelected * 0.4f);
|
||||
{
|
||||
var bounds = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, style.Selection);
|
||||
Render2D.DrawRectangle(bounds, style.SelectionBorder);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -359,6 +359,17 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
ButtonClicked?.Invoke(button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Show(Control parent, Float2 location)
|
||||
{
|
||||
// Remove last separator to make context menu look better
|
||||
int lastIndex = _panel.Children.Count - 1;
|
||||
if (lastIndex >= 0 && _panel.Children[lastIndex] is ContextMenuSeparator separator)
|
||||
separator.Dispose();
|
||||
|
||||
base.Show(parent, location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ContainsPoint(ref Float2 location, bool precise)
|
||||
{
|
||||
|
||||
@@ -168,30 +168,30 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
bool isUp = false, isLeft = false;
|
||||
if (UseAutomaticDirectionFix)
|
||||
{
|
||||
var parentMenu = parent as ContextMenu;
|
||||
if (monitorBounds.Bottom < rightBottomLocationSS.Y)
|
||||
{
|
||||
// Direction: up
|
||||
isUp = true;
|
||||
locationSS.Y -= dpiSize.Y;
|
||||
|
||||
// Offset to fix sub-menu location
|
||||
if (parent is ContextMenu menu && menu._childCM != null)
|
||||
if (parentMenu != null && parentMenu._childCM != null)
|
||||
locationSS.Y += 30.0f * dpiScale;
|
||||
}
|
||||
if (monitorBounds.Right < rightBottomLocationSS.X || _parentCM?.Direction == ContextMenuDirection.LeftDown || _parentCM?.Direction == ContextMenuDirection.LeftUp)
|
||||
if (parentMenu == null)
|
||||
{
|
||||
// Direction: left
|
||||
isLeft = true;
|
||||
|
||||
if (IsSubMenu && _parentCM != null)
|
||||
{
|
||||
locationSS.X -= _parentCM.Width + dpiSize.X;
|
||||
}
|
||||
else
|
||||
if (monitorBounds.Right < rightBottomLocationSS.X)
|
||||
{
|
||||
isLeft = true;
|
||||
locationSS.X -= dpiSize.X;
|
||||
}
|
||||
}
|
||||
else if (monitorBounds.Right < rightBottomLocationSS.X || _parentCM?.Direction == ContextMenuDirection.LeftDown || _parentCM?.Direction == ContextMenuDirection.LeftUp)
|
||||
{
|
||||
isLeft = true;
|
||||
if (IsSubMenu && _parentCM != null)
|
||||
locationSS.X -= _parentCM.Width + dpiSize.X;
|
||||
else
|
||||
locationSS.X -= dpiSize.X;
|
||||
}
|
||||
}
|
||||
|
||||
// Update direction flag
|
||||
|
||||
@@ -484,6 +484,7 @@ namespace FlaxEditor.GUI
|
||||
cm.AddSeparator();
|
||||
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
|
||||
cm.AddButton("Select all keyframes", _editor.SelectAll);
|
||||
cm.AddButton("Deselect all keyframes", _editor.DeselectAll);
|
||||
cm.AddButton("Copy all keyframes", () =>
|
||||
{
|
||||
_editor.SelectAll();
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using FlaxEditor.CustomEditors;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
@@ -713,15 +714,28 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
private void BulkSelectUpdate(bool select = true)
|
||||
{
|
||||
for (int i = 0; i < _points.Count; i++)
|
||||
{
|
||||
_points[i].IsSelected = select;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all keyframes.
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
for (int i = 0; i < _points.Count; i++)
|
||||
{
|
||||
_points[i].IsSelected = true;
|
||||
}
|
||||
BulkSelectUpdate(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselects all keyframes.
|
||||
/// </summary>
|
||||
public void DeselectAll()
|
||||
{
|
||||
BulkSelectUpdate(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -899,8 +913,8 @@ namespace FlaxEditor.GUI
|
||||
_mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)),
|
||||
_mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos))
|
||||
);
|
||||
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
|
||||
Render2D.DrawRectangle(selectionRect, Color.Orange);
|
||||
Render2D.FillRectangle(selectionRect, style.Selection);
|
||||
Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
@@ -926,34 +940,35 @@ namespace FlaxEditor.GUI
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
switch (key)
|
||||
InputOptions options = Editor.Instance.Options.Options.Input;
|
||||
if (options.SelectAll.Process(this))
|
||||
{
|
||||
SelectAll();
|
||||
UpdateTangents();
|
||||
return true;
|
||||
}
|
||||
else if (options.DeselectAll.Process(this))
|
||||
{
|
||||
DeselectAll();
|
||||
UpdateTangents();
|
||||
return true;
|
||||
}
|
||||
else if (options.Delete.Process(this))
|
||||
{
|
||||
case KeyboardKeys.Delete:
|
||||
RemoveKeyframes();
|
||||
return true;
|
||||
case KeyboardKeys.A:
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
SelectAll();
|
||||
UpdateTangents();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.C:
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
CopyKeyframes();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.V:
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
KeyframesEditorUtils.Paste(this);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (options.Copy.Process(this))
|
||||
{
|
||||
CopyKeyframes();
|
||||
return true;
|
||||
}
|
||||
else if (options.Paste.Process(this))
|
||||
{
|
||||
KeyframesEditorUtils.Paste(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -201,8 +201,9 @@ namespace FlaxEditor.GUI.Input
|
||||
if (_isSliding)
|
||||
{
|
||||
// Draw overlay
|
||||
// TODO: render nicer overlay with some glow from the borders (inside)
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Color.Orange * 0.3f);
|
||||
var bounds = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, style.Selection);
|
||||
Render2D.DrawRectangle(bounds, style.SelectionBorder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,10 @@ namespace FlaxEditor.GUI
|
||||
if (IsMouseOver || IsFocused)
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundHighlighted);
|
||||
|
||||
// Indent for drop panel items is handled by drop panel margin
|
||||
if (Parent is not DropPanel)
|
||||
textRect.Location += new Float2(Editor.Instance.Icons.ArrowRight12.Size.X + 2, 0);
|
||||
|
||||
// Draw all highlights
|
||||
if (_highlights != null)
|
||||
{
|
||||
@@ -262,6 +266,10 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
category.Visible = anyVisible;
|
||||
if (string.IsNullOrEmpty(_searchBox.Text))
|
||||
category.Close(false);
|
||||
else
|
||||
category.Open(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,9 +346,14 @@ namespace FlaxEditor.GUI
|
||||
var categoryPanel = new DropPanel
|
||||
{
|
||||
HeaderText = item.Category,
|
||||
ArrowImageOpened = new SpriteBrush(Editor.Instance.Icons.ArrowDown12),
|
||||
ArrowImageClosed = new SpriteBrush(Editor.Instance.Icons.ArrowRight12),
|
||||
EnableDropDownIcon = true,
|
||||
ItemsMargin = new Margin(28, 0, 2, 2),
|
||||
HeaderColor = Style.Current.Background,
|
||||
Parent = parent,
|
||||
};
|
||||
categoryPanel.Open(false);
|
||||
categoryPanel.Close(false);
|
||||
_categoryPanels.Add(categoryPanel);
|
||||
parent = categoryPanel;
|
||||
}
|
||||
@@ -382,6 +395,7 @@ namespace FlaxEditor.GUI
|
||||
item2.UpdateFilter(null);
|
||||
}
|
||||
category.Visible = true;
|
||||
category.Close(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ namespace FlaxEditor.GUI
|
||||
// Draw background
|
||||
if (IsDragOver && _validDragOver)
|
||||
{
|
||||
Render2D.FillRectangle(clientRect, Style.Current.BackgroundSelected * 0.6f);
|
||||
Render2D.FillRectangle(clientRect, style.Selection);
|
||||
Render2D.DrawRectangle(clientRect, style.SelectionBorder);
|
||||
}
|
||||
else if (_isPressed)
|
||||
{
|
||||
|
||||
@@ -227,8 +227,8 @@ namespace FlaxEditor.GUI.Timeline.GUI
|
||||
if (_isSelecting)
|
||||
{
|
||||
var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos);
|
||||
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
|
||||
Render2D.DrawRectangle(selectionRect, Color.Orange);
|
||||
Render2D.FillRectangle(selectionRect, style.Selection);
|
||||
Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
|
||||
}
|
||||
|
||||
DrawChildren();
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using FlaxEditor.CustomEditors;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -432,6 +433,7 @@ namespace FlaxEditor.GUI
|
||||
if (_editor.EnableKeyframesValueEdit)
|
||||
cm.AddButton("Edit all keyframes", () => _editor.EditAllKeyframes(this, location));
|
||||
cm.AddButton("Select all keyframes", _editor.SelectAll).Enabled = _editor._points.Count > 0;
|
||||
cm.AddButton("Deselect all keyframes", _editor.DeselectAll).Enabled = _editor._points.Count > 0;
|
||||
cm.AddButton("Copy all keyframes", () =>
|
||||
{
|
||||
_editor.SelectAll();
|
||||
@@ -902,7 +904,7 @@ namespace FlaxEditor.GUI
|
||||
var k = new Keyframe
|
||||
{
|
||||
Time = keyframesPos.X,
|
||||
Value = DefaultValue,
|
||||
Value = Utilities.Utils.CloneValue(DefaultValue),
|
||||
};
|
||||
OnEditingStart();
|
||||
AddKeyframe(k);
|
||||
@@ -1209,15 +1211,28 @@ namespace FlaxEditor.GUI
|
||||
}
|
||||
}
|
||||
|
||||
private void BulkSelectUpdate(bool select = true)
|
||||
{
|
||||
for (int i = 0; i < _points.Count; i++)
|
||||
{
|
||||
_points[i].IsSelected = select;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all keyframes.
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
for (int i = 0; i < _points.Count; i++)
|
||||
{
|
||||
_points[i].IsSelected = true;
|
||||
}
|
||||
BulkSelectUpdate(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselects all keyframes.
|
||||
/// </summary>
|
||||
public void DeselectAll()
|
||||
{
|
||||
BulkSelectUpdate(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1241,8 +1256,8 @@ namespace FlaxEditor.GUI
|
||||
_mainPanel.PointToParent(_contents.PointToParent(_contents._leftMouseDownPos)),
|
||||
_mainPanel.PointToParent(_contents.PointToParent(_contents._mousePos))
|
||||
);
|
||||
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
|
||||
Render2D.DrawRectangle(selectionRect, Color.Orange);
|
||||
Render2D.FillRectangle(selectionRect, style.Selection);
|
||||
Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
@@ -1268,33 +1283,33 @@ namespace FlaxEditor.GUI
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
switch (key)
|
||||
InputOptions options = Editor.Instance.Options.Options.Input;
|
||||
if (options.SelectAll.Process(this))
|
||||
{
|
||||
SelectAll();
|
||||
return true;
|
||||
}
|
||||
else if (options.DeselectAll.Process(this))
|
||||
{
|
||||
DeselectAll();
|
||||
return true;
|
||||
}
|
||||
else if (options.Delete.Process(this))
|
||||
{
|
||||
case KeyboardKeys.Delete:
|
||||
RemoveKeyframes();
|
||||
return true;
|
||||
case KeyboardKeys.A:
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
SelectAll();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.C:
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
CopyKeyframes();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyboardKeys.V:
|
||||
if (Root.GetKey(KeyboardKeys.Control))
|
||||
{
|
||||
KeyframesEditorUtils.Paste(this);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (options.Copy.Process(this))
|
||||
{
|
||||
CopyKeyframes();
|
||||
return true;
|
||||
}
|
||||
else if (options.Paste.Process(this))
|
||||
{
|
||||
KeyframesEditorUtils.Paste(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -304,7 +304,9 @@ namespace FlaxEditor.GUI.Timeline
|
||||
if (IsDragOver && _currentDragEffect != DragDropEffect.None)
|
||||
{
|
||||
var style = Style.Current;
|
||||
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), style.BackgroundSelected * 0.4f);
|
||||
var bounds = new Rectangle(Float2.Zero, Size);
|
||||
Render2D.FillRectangle(bounds, style.Selection);
|
||||
Render2D.DrawRectangle(bounds, style.SelectionBorder);
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
|
||||
@@ -187,6 +187,11 @@ namespace FlaxEditor.GUI.Timeline
|
||||
private bool _showPreviewValues = true;
|
||||
private PlaybackStates _state = PlaybackStates.Disabled;
|
||||
|
||||
/// <summary>
|
||||
/// The Track that is being dragged over. This could have a value when not dragging.
|
||||
/// </summary>
|
||||
internal Track DraggedOverTrack = null;
|
||||
|
||||
/// <summary>
|
||||
/// Flag used to mark modified timeline data.
|
||||
/// </summary>
|
||||
|
||||
@@ -774,14 +774,33 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// Updates the drag over mode based on the given mouse location.
|
||||
/// </summary>
|
||||
/// <param name="location">The location.</param>
|
||||
private void UpdateDrawPositioning(ref Float2 location)
|
||||
private void UpdateDragPositioning(ref Float2 location)
|
||||
{
|
||||
// Check collision with drag areas
|
||||
if (new Rectangle(0, 0 - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location))
|
||||
_dragOverMode = DragItemPositioning.Above;
|
||||
else if (IsCollapsed && new Rectangle(0, Height - DefaultDragInsertPositionMargin, Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location))
|
||||
_dragOverMode = DragItemPositioning.Below;
|
||||
else
|
||||
_dragOverMode = DragItemPositioning.At;
|
||||
|
||||
// Update DraggedOverTrack
|
||||
var timeline = Timeline;
|
||||
if (_dragOverMode == DragItemPositioning.None)
|
||||
{
|
||||
if (timeline != null && timeline.DraggedOverTrack == this)
|
||||
timeline.DraggedOverTrack = null;
|
||||
}
|
||||
else if (timeline != null)
|
||||
timeline.DraggedOverTrack = this;
|
||||
}
|
||||
|
||||
private void ClearDragPositioning()
|
||||
{
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
var timeline = Timeline;
|
||||
if (timeline != null && timeline.DraggedOverTrack == this)
|
||||
timeline.DraggedOverTrack = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -975,26 +994,21 @@ namespace FlaxEditor.GUI.Timeline
|
||||
}
|
||||
|
||||
// Draw drag and drop effect
|
||||
if (IsDragOver && _isDragOverHeader)
|
||||
if (IsDragOver && _timeline.DraggedOverTrack == this)
|
||||
{
|
||||
Color dragOverColor = style.BackgroundSelected * 0.6f;
|
||||
Rectangle rect;
|
||||
switch (_dragOverMode)
|
||||
{
|
||||
case DragItemPositioning.At:
|
||||
rect = textRect;
|
||||
Render2D.FillRectangle(textRect, style.Selection);
|
||||
Render2D.DrawRectangle(textRect, style.SelectionBorder);
|
||||
break;
|
||||
case DragItemPositioning.Above:
|
||||
rect = new Rectangle(textRect.X, textRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
|
||||
Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin * 0.5f - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder);
|
||||
break;
|
||||
case DragItemPositioning.Below:
|
||||
rect = new Rectangle(textRect.X, textRect.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
|
||||
break;
|
||||
default:
|
||||
rect = Rectangle.Empty;
|
||||
Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin * 0.5f, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder);
|
||||
break;
|
||||
}
|
||||
Render2D.FillRectangle(rect, dragOverColor);
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
@@ -1170,18 +1184,18 @@ namespace FlaxEditor.GUI.Timeline
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
if (result == DragDropEffect.None)
|
||||
{
|
||||
UpdateDrawPositioning(ref location);
|
||||
UpdateDragPositioning(ref location);
|
||||
|
||||
// Check if mouse is over header
|
||||
_isDragOverHeader = TestHeaderHit(ref location);
|
||||
if (_isDragOverHeader)
|
||||
{
|
||||
// Check if mouse is over arrow
|
||||
if (Timeline != null)
|
||||
Timeline.DraggedOverTrack = this;
|
||||
|
||||
// Expand node if mouse goes over arrow
|
||||
if (_children.Count > 0 && ArrowRect.Contains(location))
|
||||
{
|
||||
// Expand track
|
||||
Expand();
|
||||
}
|
||||
|
||||
result = OnDragEnterHeader(data);
|
||||
}
|
||||
@@ -1199,21 +1213,18 @@ namespace FlaxEditor.GUI.Timeline
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
|
||||
// Check if no children handled that event
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
ClearDragPositioning();
|
||||
if (result == DragDropEffect.None)
|
||||
{
|
||||
UpdateDrawPositioning(ref location);
|
||||
UpdateDragPositioning(ref location);
|
||||
|
||||
// Check if mouse is over header
|
||||
bool isDragOverHeader = TestHeaderHit(ref location);
|
||||
if (isDragOverHeader)
|
||||
{
|
||||
// Check if mouse is over arrow
|
||||
// Expand node if mouse goes over arrow
|
||||
if (_children.Count > 0 && ArrowRect.Contains(location))
|
||||
{
|
||||
// Expand track
|
||||
Expand();
|
||||
}
|
||||
|
||||
if (!_isDragOverHeader)
|
||||
result = OnDragEnterHeader(data);
|
||||
@@ -1226,10 +1237,8 @@ namespace FlaxEditor.GUI.Timeline
|
||||
}
|
||||
_isDragOverHeader = isDragOverHeader;
|
||||
|
||||
if (result == DragDropEffect.None || !isDragOverHeader)
|
||||
{
|
||||
if (result == DragDropEffect.None)
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1243,7 +1252,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
// Check if no children handled that event
|
||||
if (result == DragDropEffect.None)
|
||||
{
|
||||
UpdateDrawPositioning(ref location);
|
||||
UpdateDragPositioning(ref location);
|
||||
|
||||
// Check if mouse is over header
|
||||
if (TestHeaderHit(ref location))
|
||||
@@ -1254,7 +1263,7 @@ namespace FlaxEditor.GUI.Timeline
|
||||
|
||||
// Clear cache
|
||||
_isDragOverHeader = false;
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
ClearDragPositioning();
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1262,15 +1271,15 @@ namespace FlaxEditor.GUI.Timeline
|
||||
/// <inheritdoc />
|
||||
public override void OnDragLeave()
|
||||
{
|
||||
base.OnDragLeave();
|
||||
|
||||
// Clear cache
|
||||
if (_isDragOverHeader)
|
||||
{
|
||||
_isDragOverHeader = false;
|
||||
OnDragLeaveHeader();
|
||||
}
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
ClearDragPositioning();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
b.TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor);
|
||||
}
|
||||
}
|
||||
menu.AddButton("Select...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track";
|
||||
menu.AddButton("Retarget...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using FlaxEditor.GUI.Timeline.Undo;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Utilities;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
@@ -56,7 +58,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
throw new Exception("Invalid track data.");
|
||||
|
||||
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
|
||||
var dataBuffer = new byte[e.ValueSize];
|
||||
var propertyType = TypeUtils.GetManagedType(e.MemberTypeName);
|
||||
if (propertyType == null)
|
||||
{
|
||||
@@ -67,20 +68,40 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
return;
|
||||
}
|
||||
|
||||
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
|
||||
for (int i = 0; i < keyframesCount; i++)
|
||||
if (e.ValueSize != 0)
|
||||
{
|
||||
var time = stream.ReadSingle();
|
||||
stream.Read(dataBuffer, 0, e.ValueSize);
|
||||
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
|
||||
|
||||
keyframes[i] = new KeyframesEditor.Keyframe
|
||||
// POD value type - use raw memory
|
||||
var dataBuffer = new byte[e.ValueSize];
|
||||
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
|
||||
for (int i = 0; i < keyframesCount; i++)
|
||||
{
|
||||
Time = time,
|
||||
Value = value,
|
||||
};
|
||||
var time = stream.ReadSingle();
|
||||
stream.Read(dataBuffer, 0, e.ValueSize);
|
||||
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
|
||||
|
||||
keyframes[i] = new KeyframesEditor.Keyframe
|
||||
{
|
||||
Time = time,
|
||||
Value = value,
|
||||
};
|
||||
}
|
||||
handle.Free();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic value - use Json storage (as UTF-8)
|
||||
for (int i = 0; i < keyframesCount; i++)
|
||||
{
|
||||
var time = stream.ReadSingle();
|
||||
var len = stream.ReadInt32();
|
||||
var value = len != 0 ? FlaxEngine.Json.JsonSerializer.Deserialize(Encoding.UTF8.GetString(stream.ReadBytes(len)), propertyType) : null;
|
||||
keyframes[i] = new KeyframesEditor.Keyframe
|
||||
{
|
||||
Time = time,
|
||||
Value = value,
|
||||
};
|
||||
}
|
||||
}
|
||||
handle.Free();
|
||||
|
||||
e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType);
|
||||
e.Keyframes.SetKeyframes(keyframes);
|
||||
@@ -113,17 +134,35 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
stream.Write(propertyTypeNameData);
|
||||
stream.Write('\0');
|
||||
|
||||
var dataBuffer = new byte[e.ValueSize];
|
||||
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
|
||||
for (int i = 0; i < keyframes.Count; i++)
|
||||
if (e.ValueSize != 0)
|
||||
{
|
||||
var keyframe = keyframes[i];
|
||||
Marshal.StructureToPtr(keyframe.Value, ptr, true);
|
||||
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
|
||||
stream.Write(keyframe.Time);
|
||||
stream.Write(dataBuffer);
|
||||
// POD value type - use raw memory
|
||||
var dataBuffer = new byte[e.ValueSize];
|
||||
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
|
||||
for (int i = 0; i < keyframes.Count; i++)
|
||||
{
|
||||
var keyframe = keyframes[i];
|
||||
Marshal.StructureToPtr(keyframe.Value, ptr, true);
|
||||
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
|
||||
stream.Write(keyframe.Time);
|
||||
stream.Write(dataBuffer);
|
||||
}
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic value - use Json storage (as UTF-8)
|
||||
for (int i = 0; i < keyframes.Count; i++)
|
||||
{
|
||||
var keyframe = keyframes[i];
|
||||
stream.Write(keyframe.Time);
|
||||
var json = keyframe.Value != null ? FlaxEngine.Json.JsonSerializer.Serialize(keyframe.Value) : null;
|
||||
var len = json?.Length ?? 0;
|
||||
stream.Write(len);
|
||||
if (len > 0)
|
||||
stream.Write(Encoding.UTF8.GetBytes(json));
|
||||
}
|
||||
}
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
|
||||
private byte[] _keyframesEditingStartData;
|
||||
@@ -281,7 +320,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
private void OnKeyframesEditingEnd()
|
||||
{
|
||||
var after = EditTrackAction.CaptureData(this);
|
||||
if (!Utils.ArraysEqual(_keyframesEditingStartData, after))
|
||||
if (!FlaxEngine.Utils.ArraysEqual(_keyframesEditingStartData, after))
|
||||
Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after));
|
||||
_keyframesEditingStartData = null;
|
||||
}
|
||||
@@ -308,7 +347,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
/// <returns>The default value.</returns>
|
||||
protected virtual object GetDefaultValue(Type propertyType)
|
||||
{
|
||||
return Activator.CreateInstance(propertyType);
|
||||
var value = TypeUtils.GetDefaultValue(new ScriptType(propertyType));
|
||||
if (value == null)
|
||||
value = Activator.CreateInstance(propertyType);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -424,6 +424,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
|
||||
{ typeof(Guid), KeyframesPropertyTrack.GetArchetype() },
|
||||
{ typeof(DateTime), KeyframesPropertyTrack.GetArchetype() },
|
||||
{ typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() },
|
||||
{ typeof(LocalizedString), KeyframesPropertyTrack.GetArchetype() },
|
||||
{ typeof(string), StringPropertyTrack.GetArchetype() },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Assertions;
|
||||
using FlaxEngine.GUI;
|
||||
@@ -43,7 +44,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
/// <summary>
|
||||
/// The TreeNode that is being dragged over. This could have a value when not dragging.
|
||||
/// </summary>
|
||||
public TreeNode DraggedOverNode = null;
|
||||
internal TreeNode DraggedOverNode = null;
|
||||
|
||||
/// <summary>
|
||||
/// Action fired when tree nodes selection gets changed.
|
||||
@@ -315,10 +316,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all expanded nodes
|
||||
/// </summary>
|
||||
public void SelectAllExpanded()
|
||||
private void BulkSelectUpdateExpanded(bool select = true)
|
||||
{
|
||||
if (_supportMultiSelect)
|
||||
{
|
||||
@@ -327,7 +325,8 @@ namespace FlaxEditor.GUI.Tree
|
||||
|
||||
// Update selection
|
||||
Selection.Clear();
|
||||
WalkSelectExpandedTree(Selection, _children[0] as TreeNode);
|
||||
if (select)
|
||||
WalkSelectExpandedTree(Selection, _children[0] as TreeNode);
|
||||
|
||||
// Check if changed
|
||||
if (Selection.Count != prev.Count || !Selection.SequenceEqual(prev))
|
||||
@@ -338,6 +337,22 @@ namespace FlaxEditor.GUI.Tree
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all expanded nodes
|
||||
/// </summary>
|
||||
public void SelectAllExpanded()
|
||||
{
|
||||
BulkSelectUpdateExpanded(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselect all nodes
|
||||
/// </summary>
|
||||
public void DeselectAll()
|
||||
{
|
||||
BulkSelectUpdateExpanded(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
@@ -472,14 +487,19 @@ namespace FlaxEditor.GUI.Tree
|
||||
// Check if can use multi selection
|
||||
if (_supportMultiSelect)
|
||||
{
|
||||
bool isCtrlDown = Root.GetKey(KeyboardKeys.Control);
|
||||
InputOptions options = Editor.Instance.Options.Options.Input;
|
||||
|
||||
// Select all expanded nodes
|
||||
if (key == KeyboardKeys.A && isCtrlDown)
|
||||
if (options.SelectAll.Process(this))
|
||||
{
|
||||
SelectAllExpanded();
|
||||
return true;
|
||||
}
|
||||
else if (options.DeselectAll.Process(this))
|
||||
{
|
||||
DeselectAll();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnKeyDown(key);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
/// <summary>
|
||||
/// The default drag insert position margin.
|
||||
/// </summary>
|
||||
public const float DefaultDragInsertPositionMargin = 2.0f;
|
||||
public const float DefaultDragInsertPositionMargin = 3.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The default node offset on Y axis.
|
||||
@@ -42,6 +42,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
|
||||
private DragItemPositioning _dragOverMode;
|
||||
private bool _isDragOverHeader;
|
||||
private static ulong _dragEndFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text.
|
||||
@@ -546,14 +547,33 @@ namespace FlaxEditor.GUI.Tree
|
||||
/// Updates the drag over mode based on the given mouse location.
|
||||
/// </summary>
|
||||
/// <param name="location">The location.</param>
|
||||
private void UpdateDrawPositioning(ref Float2 location)
|
||||
private void UpdateDragPositioning(ref Float2 location)
|
||||
{
|
||||
// Check collision with drag areas
|
||||
if (new Rectangle(_headerRect.X, _headerRect.Y - DefaultDragInsertPositionMargin - DefaultNodeOffsetY, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location))
|
||||
_dragOverMode = DragItemPositioning.Above;
|
||||
else if ((IsCollapsed || !HasAnyVisibleChild) && new Rectangle(_headerRect.X, _headerRect.Bottom - DefaultDragInsertPositionMargin, _headerRect.Width, DefaultDragInsertPositionMargin * 2.0f).Contains(location))
|
||||
_dragOverMode = DragItemPositioning.Below;
|
||||
else
|
||||
_dragOverMode = DragItemPositioning.At;
|
||||
|
||||
// Update DraggedOverNode
|
||||
var tree = ParentTree;
|
||||
if (_dragOverMode == DragItemPositioning.None)
|
||||
{
|
||||
if (tree != null && tree.DraggedOverNode == this)
|
||||
tree.DraggedOverNode = null;
|
||||
}
|
||||
else if (tree != null)
|
||||
tree.DraggedOverNode = this;
|
||||
}
|
||||
|
||||
private void ClearDragPositioning()
|
||||
{
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
var tree = ParentTree;
|
||||
if (tree != null && tree.DraggedOverNode == this)
|
||||
tree.DraggedOverNode = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -661,27 +681,19 @@ namespace FlaxEditor.GUI.Tree
|
||||
// Draw drag and drop effect
|
||||
if (IsDragOver && _tree.DraggedOverNode == this)
|
||||
{
|
||||
Color dragOverColor = style.BackgroundSelected;
|
||||
Rectangle rect;
|
||||
switch (_dragOverMode)
|
||||
{
|
||||
case DragItemPositioning.At:
|
||||
dragOverColor *= 0.6f;
|
||||
rect = textRect;
|
||||
Render2D.FillRectangle(textRect, style.Selection);
|
||||
Render2D.DrawRectangle(textRect, style.SelectionBorder);
|
||||
break;
|
||||
case DragItemPositioning.Above:
|
||||
dragOverColor *= 1.2f;
|
||||
rect = new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
|
||||
Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Top - DefaultDragInsertPositionMargin * 0.5f - DefaultNodeOffsetY - _margin.Top, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder);
|
||||
break;
|
||||
case DragItemPositioning.Below:
|
||||
dragOverColor *= 1.2f;
|
||||
rect = new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin, textRect.Width, DefaultDragInsertPositionMargin * 2.0f);
|
||||
break;
|
||||
default:
|
||||
rect = Rectangle.Empty;
|
||||
Render2D.DrawRectangle(new Rectangle(textRect.X, textRect.Bottom + _margin.Bottom - DefaultDragInsertPositionMargin * 0.5f, textRect.Width, DefaultDragInsertPositionMargin), style.SelectionBorder);
|
||||
break;
|
||||
}
|
||||
Render2D.FillRectangle(rect, dragOverColor);
|
||||
}
|
||||
|
||||
// Base
|
||||
@@ -736,9 +748,8 @@ namespace FlaxEditor.GUI.Tree
|
||||
UpdateMouseOverFlags(location);
|
||||
|
||||
// Clear flag for left button
|
||||
if (button == MouseButton.Left)
|
||||
if (button == MouseButton.Left && _isMouseDown)
|
||||
{
|
||||
// Clear flag
|
||||
_isMouseDown = false;
|
||||
_mouseDownTime = -1;
|
||||
}
|
||||
@@ -746,6 +757,10 @@ namespace FlaxEditor.GUI.Tree
|
||||
// Check if mouse hits bar and node isn't a root
|
||||
if (_mouseOverHeader)
|
||||
{
|
||||
// Skip mouse up event right after drag drop ends
|
||||
if (button == MouseButton.Left && Engine.FrameCount - _dragEndFrame < 10)
|
||||
return true;
|
||||
|
||||
// Prevent from selecting node when user is just clicking at an arrow
|
||||
if (!_mouseOverArrow)
|
||||
{
|
||||
@@ -937,20 +952,18 @@ namespace FlaxEditor.GUI.Tree
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
if (result == DragDropEffect.None)
|
||||
{
|
||||
UpdateDrawPositioning(ref location);
|
||||
if (ParentTree != null)
|
||||
ParentTree.DraggedOverNode = this;
|
||||
UpdateDragPositioning(ref location);
|
||||
|
||||
// Check if mouse is over header
|
||||
_isDragOverHeader = TestHeaderHit(ref location);
|
||||
if (_isDragOverHeader)
|
||||
{
|
||||
// Check if mouse is over arrow
|
||||
if (ParentTree != null)
|
||||
ParentTree.DraggedOverNode = this;
|
||||
|
||||
// Expand node if mouse goes over arrow
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
|
||||
{
|
||||
// Expand node (no animation)
|
||||
Expand(true);
|
||||
}
|
||||
|
||||
result = OnDragEnterHeader(data);
|
||||
}
|
||||
@@ -968,26 +981,31 @@ namespace FlaxEditor.GUI.Tree
|
||||
var result = base.OnDragMove(ref location, data);
|
||||
|
||||
// Check if no children handled that event
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
ClearDragPositioning();
|
||||
if (result == DragDropEffect.None)
|
||||
{
|
||||
UpdateDrawPositioning(ref location);
|
||||
UpdateDragPositioning(ref location);
|
||||
|
||||
// Check if mouse is over header
|
||||
bool isDragOverHeader = TestHeaderHit(ref location);
|
||||
if (isDragOverHeader)
|
||||
{
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
|
||||
{
|
||||
// Expand node (no animation)
|
||||
Expand(true);
|
||||
}
|
||||
if (ParentTree != null)
|
||||
ParentTree.DraggedOverNode = this;
|
||||
|
||||
// Expand node if mouse goes over arrow
|
||||
if (ArrowRect.Contains(location) && HasAnyVisibleChild)
|
||||
Expand(true);
|
||||
|
||||
if (!_isDragOverHeader)
|
||||
result = OnDragEnterHeader(data);
|
||||
else
|
||||
result = OnDragMoveHeader(data);
|
||||
}
|
||||
else if (_isDragOverHeader)
|
||||
{
|
||||
OnDragLeaveHeader();
|
||||
}
|
||||
_isDragOverHeader = isDragOverHeader;
|
||||
|
||||
if (result == DragDropEffect.None)
|
||||
@@ -1005,7 +1023,8 @@ namespace FlaxEditor.GUI.Tree
|
||||
// Check if no children handled that event
|
||||
if (result == DragDropEffect.None)
|
||||
{
|
||||
UpdateDrawPositioning(ref location);
|
||||
UpdateDragPositioning(ref location);
|
||||
_dragEndFrame = Engine.FrameCount;
|
||||
|
||||
// Check if mouse is over header
|
||||
if (TestHeaderHit(ref location))
|
||||
@@ -1016,9 +1035,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
|
||||
// Clear cache
|
||||
_isDragOverHeader = false;
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
if (ParentTree != null)
|
||||
ParentTree.DraggedOverNode = null;
|
||||
ClearDragPositioning();
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1032,7 +1049,7 @@ namespace FlaxEditor.GUI.Tree
|
||||
_isDragOverHeader = false;
|
||||
OnDragLeaveHeader();
|
||||
}
|
||||
_dragOverMode = DragItemPositioning.None;
|
||||
ClearDragPositioning();
|
||||
|
||||
base.OnDragLeave();
|
||||
}
|
||||
|
||||
@@ -263,6 +263,8 @@ namespace FlaxEditor.Gizmo
|
||||
// Note: because selection may contain objects and their children we have to split them and get only parents.
|
||||
// Later during transformation we apply translation/scale/rotation only on them (children inherit transformations)
|
||||
SceneGraphTools.BuildNodesParents(_selection, _selectionParents);
|
||||
|
||||
base.OnSelectionChanged(newSelection);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -437,8 +437,6 @@ namespace FlaxEditor.Gizmo
|
||||
{
|
||||
case Mode.Translate:
|
||||
UpdateTranslateScale();
|
||||
if (Owner.SnapToVertex)
|
||||
UpdateVertexSnapping();
|
||||
break;
|
||||
case Mode.Scale:
|
||||
UpdateTranslateScale();
|
||||
@@ -447,6 +445,8 @@ namespace FlaxEditor.Gizmo
|
||||
UpdateRotate(dt);
|
||||
break;
|
||||
}
|
||||
if (Owner.SnapToVertex)
|
||||
UpdateVertexSnapping();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -553,41 +553,21 @@ namespace FlaxEditor.Gizmo
|
||||
for (int i = 0; i < SelectionCount; i++)
|
||||
{
|
||||
var obj = GetSelectedObject(i);
|
||||
if (obj.CanVertexSnap && obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance)
|
||||
if (obj.RayCastSelf(ref ray, out var distance, out _) && distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestObject = obj;
|
||||
}
|
||||
}
|
||||
if (closestObject == null)
|
||||
{
|
||||
// Find the closest object in selection (in case ray didn't hit anything)
|
||||
for (int i = 0; i < SelectionCount; i++)
|
||||
{
|
||||
var obj = GetSelectedObject(i);
|
||||
if (obj.CanVertexSnap)
|
||||
{
|
||||
GetSelectedObjectsBounds(out var bounds, out _);
|
||||
CollisionsHelper.ClosestPointBoxPoint(ref bounds, ref ray.Ray.Position, out var point);
|
||||
var distance = Vector3.Distance(ref point, ref ray.Ray.Position);
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestObject = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_vertexSnapObject = closestObject;
|
||||
if (closestObject == null)
|
||||
return;
|
||||
return; // ignore it if there is nothing under the mouse closestObject is only null if ray caster missed everything or Selection Count == 0
|
||||
|
||||
// Find the closest vertex to bounding box point (collision detection approximation)
|
||||
var closestPoint = ray.Ray.GetPoint(closestDistance);
|
||||
if (!closestObject.OnVertexSnap(ref closestPoint, out _vertexSnapPoint))
|
||||
_vertexSnapObject = closestObject;
|
||||
if (!closestObject.OnVertexSnap(ref ray.Ray, closestDistance, out _vertexSnapPoint))
|
||||
{
|
||||
// Failed to get the closest point
|
||||
_vertexSnapPoint = closestPoint;
|
||||
// The OnVertexSnap is unimplemented or failed to get point return because there is nothing to do
|
||||
_vertexSnapPoint = Vector3.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
// Transform back to the local space of the object to work when moving it
|
||||
@@ -619,12 +599,11 @@ namespace FlaxEditor.Gizmo
|
||||
for (int i = 0; i < SelectionCount; i++)
|
||||
rayCast.ExcludeObjects.Add(GetSelectedObject(i));
|
||||
var hit = Owner.SceneGraphRoot.RayCast(ref rayCast, out var distance, out var _);
|
||||
if (hit != null && hit.CanVertexSnap)
|
||||
if (hit != null)
|
||||
{
|
||||
var point = rayCast.Ray.GetPoint(distance);
|
||||
if (hit.OnVertexSnap(ref point, out var pointSnapped)
|
||||
if (hit.OnVertexSnap(ref rayCast.Ray, distance, out var pointSnapped)
|
||||
//&& Vector3.Distance(point, pointSnapped) <= 25.0f
|
||||
)
|
||||
)
|
||||
{
|
||||
_vertexSnapObjectTo = hit;
|
||||
_vertexSnapPointTo = hit.Transform.WorldToLocal(pointSnapped);
|
||||
@@ -712,5 +691,12 @@ namespace FlaxEditor.Gizmo
|
||||
protected virtual void OnDuplicate()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnSelectionChanged(List<SceneGraphNode> newSelection)
|
||||
{
|
||||
EndVertexSnapping();
|
||||
UpdateGizmoPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,6 +509,21 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CanSetToRoot(Prefab* prefab, Actor* ta
|
||||
return true;
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(void) EditorInternal_GetPrefabNestedObject(Guid* prefabId, Guid* prefabObjectId, Guid* outPrefabId, Guid* outPrefabObjectId)
|
||||
{
|
||||
*outPrefabId = Guid::Empty;
|
||||
*outPrefabObjectId = Guid::Empty;
|
||||
const auto prefab = Content::Load<Prefab>(*prefabId);
|
||||
if (!prefab)
|
||||
return;
|
||||
const ISerializable::DeserializeStream** prefabObjectDataPtr = prefab->ObjectsDataCache.TryGet(*prefabObjectId);
|
||||
if (!prefabObjectDataPtr)
|
||||
return;
|
||||
const ISerializable::DeserializeStream& prefabObjectData = **prefabObjectDataPtr;
|
||||
JsonTools::GetGuidIfValid(*outPrefabId, prefabObjectData, "PrefabID");
|
||||
JsonTools::GetGuidIfValid(*outPrefabObjectId, prefabObjectData, "PrefabObjectID");
|
||||
}
|
||||
|
||||
DEFINE_INTERNAL_CALL(float) EditorInternal_GetAnimationTime(AnimatedModel* animatedModel)
|
||||
{
|
||||
return animatedModel && animatedModel->GraphInstance.State.Count() == 1 ? animatedModel->GraphInstance.State[0].Animation.TimePosition : 0.0f;
|
||||
|
||||
@@ -278,13 +278,7 @@ void ManagedEditor::Update()
|
||||
void ManagedEditor::Exit()
|
||||
{
|
||||
if (WasExitCalled)
|
||||
{
|
||||
// Ups xD
|
||||
LOG(Warning, "Managed Editor exit called after exit or before init.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set flag
|
||||
WasExitCalled = true;
|
||||
|
||||
// Skip if managed object is missing
|
||||
|
||||
@@ -225,8 +225,15 @@ namespace FlaxEditor.Modules
|
||||
throw new ArgumentException("Missing prefab to apply.");
|
||||
PrefabApplying?.Invoke(prefab, instance);
|
||||
|
||||
// When applying changes to prefab from actor in level ignore it's root transformation (see ActorEditor.ProcessDiff)
|
||||
var originalTransform = instance.LocalTransform;
|
||||
if (instance.IsPrefabRoot && instance.Scene != null)
|
||||
instance.LocalTransform = prefab.GetDefaultInstance().Transform;
|
||||
|
||||
// Call backend
|
||||
if (PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance)))
|
||||
var failed = PrefabManager.Internal_ApplyAll(FlaxEngine.Object.GetUnmanagedPtr(instance));
|
||||
instance.LocalTransform = originalTransform;
|
||||
if (failed)
|
||||
throw new Exception("Failed to apply the prefab. See log to learn more.");
|
||||
|
||||
PrefabApplied?.Invoke(prefab, instance);
|
||||
|
||||
@@ -60,13 +60,26 @@ namespace FlaxEditor.Modules
|
||||
{
|
||||
}
|
||||
|
||||
private void BulkScenesSelectUpdate(bool select = true)
|
||||
{
|
||||
// Blank list deselects all
|
||||
Select(select ? Editor.Scene.Root.ChildNodes : new List<SceneGraphNode>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all scenes.
|
||||
/// </summary>
|
||||
public void SelectAllScenes()
|
||||
{
|
||||
// Select all scenes (linked to the root node)
|
||||
Select(Editor.Scene.Root.ChildNodes);
|
||||
BulkScenesSelectUpdate(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deselects all scenes.
|
||||
/// </summary>
|
||||
public void DeselectAllScenes()
|
||||
{
|
||||
BulkScenesSelectUpdate(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -320,7 +333,7 @@ namespace FlaxEditor.Modules
|
||||
actorNode.PostSpawn();
|
||||
|
||||
// Create undo action
|
||||
IUndoAction action = new DeleteActorsAction(new List<SceneGraphNode>(1) { actorNode }, true);
|
||||
IUndoAction action = new DeleteActorsAction(actorNode, true);
|
||||
if (autoSelect)
|
||||
{
|
||||
var before = Selection.ToArray();
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.IO;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.SceneGraph.Actors;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.Modules
|
||||
@@ -454,6 +455,41 @@ namespace FlaxEditor.Modules
|
||||
Profiler.EndEvent();
|
||||
}
|
||||
|
||||
private Dictionary<ContainerControl, Float2> _uiRootSizes;
|
||||
|
||||
internal void OnSaveStart(ContainerControl uiRoot)
|
||||
{
|
||||
// Force viewport UI to have fixed size during scene/prefabs saving to result in stable data (less mess in version control diffs)
|
||||
if (_uiRootSizes == null)
|
||||
_uiRootSizes = new Dictionary<ContainerControl, Float2>();
|
||||
_uiRootSizes[uiRoot] = uiRoot.Size;
|
||||
uiRoot.Size = new Float2(1920, 1080);
|
||||
}
|
||||
|
||||
internal void OnSaveEnd(ContainerControl uiRoot)
|
||||
{
|
||||
// Restore cached size of the UI root container
|
||||
if (_uiRootSizes != null && _uiRootSizes.Remove(uiRoot, out var size))
|
||||
{
|
||||
uiRoot.Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSceneSaving(Scene scene, Guid sceneId)
|
||||
{
|
||||
OnSaveStart(RootControl.GameRoot);
|
||||
}
|
||||
|
||||
private void OnSceneSaved(Scene scene, Guid sceneId)
|
||||
{
|
||||
OnSaveEnd(RootControl.GameRoot);
|
||||
}
|
||||
|
||||
private void OnSceneSaveError(Scene scene, Guid sceneId)
|
||||
{
|
||||
OnSaveEnd(RootControl.GameRoot);
|
||||
}
|
||||
|
||||
private void OnSceneLoaded(Scene scene, Guid sceneId)
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
@@ -659,6 +695,9 @@ namespace FlaxEditor.Modules
|
||||
Root = new ScenesRootNode();
|
||||
|
||||
// Bind events
|
||||
Level.SceneSaving += OnSceneSaving;
|
||||
Level.SceneSaved += OnSceneSaved;
|
||||
Level.SceneSaveError += OnSceneSaveError;
|
||||
Level.SceneLoaded += OnSceneLoaded;
|
||||
Level.SceneUnloading += OnSceneUnloading;
|
||||
Level.ActorSpawned += OnActorSpawned;
|
||||
@@ -673,6 +712,9 @@ namespace FlaxEditor.Modules
|
||||
public override void OnExit()
|
||||
{
|
||||
// Unbind events
|
||||
Level.SceneSaving -= OnSceneSaving;
|
||||
Level.SceneSaved -= OnSceneSaved;
|
||||
Level.SceneSaveError -= OnSceneSaveError;
|
||||
Level.SceneLoaded -= OnSceneLoaded;
|
||||
Level.SceneUnloading -= OnSceneUnloading;
|
||||
Level.ActorSpawned -= OnActorSpawned;
|
||||
|
||||
@@ -264,11 +264,14 @@ namespace FlaxEditor.Modules
|
||||
_enterPlayFocusedWindow = gameWin;
|
||||
|
||||
// Show Game widow if hidden
|
||||
if (gameWin != null && gameWin.FocusOnPlay)
|
||||
if (gameWin != null)
|
||||
{
|
||||
gameWin.FocusGameViewport();
|
||||
if (gameWin.FocusOnPlay)
|
||||
gameWin.FocusGameViewport();
|
||||
gameWin.SetWindowMode(Editor.Options.Options.Interface.DefaultGameWindowMode);
|
||||
}
|
||||
|
||||
|
||||
Editor.Log("[PlayMode] Enter");
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace FlaxEditor.Modules
|
||||
private ContextMenuButton _menuEditDelete;
|
||||
private ContextMenuButton _menuEditDuplicate;
|
||||
private ContextMenuButton _menuEditSelectAll;
|
||||
private ContextMenuButton _menuEditDeselectAll;
|
||||
private ContextMenuButton _menuEditFind;
|
||||
private ContextMenuButton _menuSceneMoveActorToViewport;
|
||||
private ContextMenuButton _menuSceneAlignActorWithViewport;
|
||||
@@ -554,6 +555,7 @@ namespace FlaxEditor.Modules
|
||||
_menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate);
|
||||
cm.AddSeparator();
|
||||
_menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes);
|
||||
_menuEditDeselectAll = cm.AddButton("Deselect all", inputOptions.DeselectAll, Editor.SceneEditing.DeselectAllScenes);
|
||||
_menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors);
|
||||
_menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search);
|
||||
cm.AddSeparator();
|
||||
@@ -673,6 +675,7 @@ namespace FlaxEditor.Modules
|
||||
_menuEditDelete.ShortKeys = inputOptions.Delete.ToString();
|
||||
_menuEditDuplicate.ShortKeys = inputOptions.Duplicate.ToString();
|
||||
_menuEditSelectAll.ShortKeys = inputOptions.SelectAll.ToString();
|
||||
_menuEditDeselectAll.ShortKeys = inputOptions.DeselectAll.ToString();
|
||||
_menuEditFind.ShortKeys = inputOptions.Search.ToString();
|
||||
_menuGamePlayGame.ShortKeys = inputOptions.Play.ToString();
|
||||
_menuGamePlayCurrentScenes.ShortKeys = inputOptions.PlayCurrentScenes.ToString();
|
||||
@@ -739,6 +742,17 @@ namespace FlaxEditor.Modules
|
||||
playActionGroup.SelectedChanged = SetPlayAction;
|
||||
Editor.Options.OptionsChanged += options => { playActionGroup.Selected = options.Interface.PlayButtonAction; };
|
||||
|
||||
var windowModesGroup = new ContextMenuSingleSelectGroup<InterfaceOptions.GameWindowMode>();
|
||||
var windowTypeMenu = _toolStripPlay.ContextMenu.AddChildMenu("Game window mode");
|
||||
windowModesGroup.AddItem("Docked", InterfaceOptions.GameWindowMode.Docked, null, "Shows the game window docked, inside the editor");
|
||||
windowModesGroup.AddItem("Popup", InterfaceOptions.GameWindowMode.PopupWindow, null, "Shows the game window as a popup");
|
||||
windowModesGroup.AddItem("Maximized", InterfaceOptions.GameWindowMode.MaximizedWindow, null, "Shows the game window maximized (Same as pressing F11)");
|
||||
windowModesGroup.AddItem("Borderless", InterfaceOptions.GameWindowMode.BorderlessWindow, null, "Shows the game window borderless");
|
||||
windowModesGroup.AddItemsToContextMenu(windowTypeMenu.ContextMenu);
|
||||
windowModesGroup.Selected = Editor.Options.Options.Interface.DefaultGameWindowMode;
|
||||
windowModesGroup.SelectedChanged = SetGameWindowMode;
|
||||
Editor.Options.OptionsChanged += options => { windowModesGroup.Selected = options.Interface.DefaultGameWindowMode; };
|
||||
|
||||
_toolStripPause = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Pause64, Editor.Simulation.RequestResumeOrPause).LinkTooltip($"Pause/Resume game ({inputOptions.Pause})");
|
||||
_toolStripStep = (ToolStripButton)ToolStrip.AddButton(Editor.Icons.Skip64, Editor.Simulation.RequestPlayOneFrame).LinkTooltip("Step one frame in game");
|
||||
|
||||
@@ -871,6 +885,7 @@ namespace FlaxEditor.Modules
|
||||
_menuEditDelete.Enabled = hasSthSelected;
|
||||
_menuEditDuplicate.Enabled = hasSthSelected;
|
||||
_menuEditSelectAll.Enabled = Level.IsAnySceneLoaded;
|
||||
_menuEditDeselectAll.Enabled = hasSthSelected;
|
||||
|
||||
control.PerformLayout();
|
||||
}
|
||||
@@ -1043,6 +1058,13 @@ namespace FlaxEditor.Modules
|
||||
Editor.Options.Apply(options);
|
||||
}
|
||||
|
||||
private void SetGameWindowMode(InterfaceOptions.GameWindowMode newGameWindowMode)
|
||||
{
|
||||
var options = Editor.Options.Options;
|
||||
options.Interface.DefaultGameWindowMode = newGameWindowMode;
|
||||
Editor.Options.Apply(options);
|
||||
}
|
||||
|
||||
private void OnMainWindowClosing()
|
||||
{
|
||||
// Clear UI references (GUI cannot be used after window closing)
|
||||
|
||||
@@ -56,6 +56,10 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Common"), EditorOrder(190)]
|
||||
public InputBinding SelectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Control);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "Ctrl+Shift+A")]
|
||||
[EditorDisplay("Common"), EditorOrder(195)]
|
||||
public InputBinding DeselectAll = new InputBinding(KeyboardKeys.A, KeyboardKeys.Shift, KeyboardKeys.Control);
|
||||
|
||||
[DefaultValue(typeof(InputBinding), "F")]
|
||||
[EditorDisplay("Common"), EditorOrder(200)]
|
||||
public InputBinding FocusSelection = new InputBinding(KeyboardKeys.F);
|
||||
|
||||
@@ -90,6 +90,32 @@ namespace FlaxEditor.Options
|
||||
PlayScenes,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Available window modes for the game window.
|
||||
/// </summary>
|
||||
public enum GameWindowMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows the game window docked, inside the editor.
|
||||
/// </summary>
|
||||
Docked,
|
||||
|
||||
/// <summary>
|
||||
/// Shows the game window as a popup.
|
||||
/// </summary>
|
||||
PopupWindow,
|
||||
|
||||
/// <summary>
|
||||
/// Shows the game window maximized. (Same as pressing F11)
|
||||
/// </summary>
|
||||
MaximizedWindow,
|
||||
|
||||
/// <summary>
|
||||
/// Shows the game window borderless.
|
||||
/// </summary>
|
||||
BorderlessWindow,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required.
|
||||
/// </summary>
|
||||
@@ -229,6 +255,13 @@ namespace FlaxEditor.Options
|
||||
[EditorDisplay("Play In-Editor", "Play Button Action"), EditorOrder(410)]
|
||||
public PlayAction PlayButtonAction { get; set; } = PlayAction.PlayScenes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating how the game window should be displayed when the game is launched.
|
||||
/// </summary>
|
||||
[DefaultValue(GameWindowMode.Docked)]
|
||||
[EditorDisplay("Play In-Editor", "Game Window Mode"), EditorOrder(420), Tooltip("Determines how the game window is displayed when the game is launched.")]
|
||||
public GameWindowMode DefaultGameWindowMode { get; set; } = GameWindowMode.Docked;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the number of game clients to launch when building and/or running cooked game.
|
||||
/// </summary>
|
||||
|
||||
@@ -212,6 +212,14 @@ namespace FlaxEditor.Options
|
||||
string styleName = themeOptions.SelectedStyle;
|
||||
if (styleName != ThemeOptions.DefaultName && styleName != ThemeOptions.LightDefault && themeOptions.Styles.TryGetValue(styleName, out var style) && style != null)
|
||||
{
|
||||
// Setup defaults for newly added components that might be missing
|
||||
if (style.Selection == Color.Transparent && style.SelectionBorder == Color.Transparent)
|
||||
{
|
||||
// [Deprecated on 6.03.2024, expires on 6.03.2026]
|
||||
style.Selection = Color.Orange * 0.4f;
|
||||
style.SelectionBorder = Color.Orange;
|
||||
}
|
||||
|
||||
Style.Current = style;
|
||||
}
|
||||
else
|
||||
@@ -258,6 +266,8 @@ namespace FlaxEditor.Options
|
||||
TextBoxBackgroundSelected = Color.FromBgra(0xFF3F3F46),
|
||||
CollectionBackgroundColor = Color.FromBgra(0x14CCCCCC),
|
||||
ProgressNormal = Color.FromBgra(0xFF0ad328),
|
||||
Selection = Color.Orange * 0.4f,
|
||||
SelectionBorder = Color.Orange,
|
||||
|
||||
Statusbar = new Style.StatusbarStyle
|
||||
{
|
||||
@@ -318,6 +328,8 @@ namespace FlaxEditor.Options
|
||||
TextBoxBackgroundSelected = new Color(0.73f, 0.73f, 0.80f, 1f),
|
||||
CollectionBackgroundColor = new Color(0.85f, 0.85f, 0.88f, 1f),
|
||||
ProgressNormal = new Color(0.03f, 0.65f, 0.12f, 1f),
|
||||
Selection = Color.Orange * 0.4f,
|
||||
SelectionBorder = Color.Orange,
|
||||
|
||||
// Fonts
|
||||
FontTitle = options.Interface.TitleFont.GetFont(),
|
||||
|
||||
@@ -316,41 +316,7 @@ namespace FlaxEditor.SceneGraph
|
||||
{
|
||||
base.OnParentChanged();
|
||||
|
||||
// Update UI (special case if actor is spawned and added to existing scene tree)
|
||||
var parentTreeNode = (parentNode as ActorNode)?.TreeNode;
|
||||
if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked)
|
||||
{
|
||||
parentTreeNode.IsLayoutLocked = true;
|
||||
_treeNode.Parent = parentTreeNode;
|
||||
_treeNode.IndexInParent = _actor.OrderInParent;
|
||||
parentTreeNode.IsLayoutLocked = false;
|
||||
|
||||
// Skip UI update if node won't be in a view
|
||||
if (parentTreeNode.IsCollapsed)
|
||||
{
|
||||
TreeNode.UnlockChildrenRecursive();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to perform layout at the level where it makes it the most performant (the least computations)
|
||||
var tree = parentTreeNode.ParentTree;
|
||||
if (tree != null)
|
||||
{
|
||||
if (tree.Parent is FlaxEngine.GUI.Panel treeParent)
|
||||
treeParent.PerformLayout();
|
||||
else
|
||||
tree.PerformLayout();
|
||||
}
|
||||
else
|
||||
{
|
||||
parentTreeNode.PerformLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_treeNode.Parent = parentTreeNode;
|
||||
}
|
||||
_treeNode.OnParentChanged(_actor, parentNode as ActorNode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if USE_LARGE_WORLDS
|
||||
using Real = System.Double;
|
||||
#else
|
||||
using Real = System.Single;
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.Content;
|
||||
@@ -25,18 +31,20 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnVertexSnap(ref Vector3 point, out Vector3 result)
|
||||
public override bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
|
||||
{
|
||||
result = point;
|
||||
// Find the closest vertex to bounding box point (collision detection approximation)
|
||||
result = ray.GetPoint(hitDistance);
|
||||
var model = ((StaticModel)Actor).Model;
|
||||
if (model && !model.WaitForLoaded())
|
||||
{
|
||||
// TODO: move to C++ and use cached vertex buffer internally inside the Mesh
|
||||
if (_vertices == null)
|
||||
_vertices = new();
|
||||
var pointLocal = (Float3)Actor.Transform.WorldToLocal(point);
|
||||
var minDistance = float.MaxValue;
|
||||
foreach (var lod in model.LODs)
|
||||
var pointLocal = (Float3)Actor.Transform.WorldToLocal(result);
|
||||
var minDistance = Real.MaxValue;
|
||||
var lodIndex = 0; // TODO: use LOD index based on the game view
|
||||
var lod = model.LODs[lodIndex];
|
||||
{
|
||||
var hit = false;
|
||||
foreach (var mesh in lod.Meshes)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Actions;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Drag;
|
||||
@@ -14,7 +15,6 @@ using FlaxEditor.Windows.Assets;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
using Object = FlaxEngine.Object;
|
||||
|
||||
namespace FlaxEditor.SceneGraph.GUI
|
||||
{
|
||||
@@ -82,8 +82,51 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
internal void OnParentChanged(Actor actor, ActorNode parentNode)
|
||||
{
|
||||
// Update cached value
|
||||
_orderInParent = actor.OrderInParent;
|
||||
|
||||
// Update UI (special case if actor is spawned and added to existing scene tree)
|
||||
var parentTreeNode = parentNode?.TreeNode;
|
||||
if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked)
|
||||
{
|
||||
parentTreeNode.IsLayoutLocked = true;
|
||||
Parent = parentTreeNode;
|
||||
IndexInParent = _orderInParent;
|
||||
parentTreeNode.IsLayoutLocked = false;
|
||||
|
||||
// Skip UI update if node won't be in a view
|
||||
if (parentTreeNode.IsCollapsed)
|
||||
{
|
||||
UnlockChildrenRecursive();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to perform layout at the level where it makes it the most performant (the least computations)
|
||||
var tree = parentTreeNode.ParentTree;
|
||||
if (tree != null)
|
||||
{
|
||||
if (tree.Parent is Panel treeParent)
|
||||
treeParent.PerformLayout();
|
||||
else
|
||||
tree.PerformLayout();
|
||||
}
|
||||
else
|
||||
{
|
||||
parentTreeNode.PerformLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Parent = parentTreeNode;
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnOrderInParentChanged()
|
||||
{
|
||||
// Use cached value to check if we need to update UI layout (and update siblings order at once)
|
||||
if (Parent is ActorTreeNode parent)
|
||||
{
|
||||
var anyChanged = false;
|
||||
@@ -419,134 +462,6 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
_dragHandlers.OnDragLeave();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class ReparentAction : IUndoAction
|
||||
{
|
||||
[Serialize]
|
||||
private Guid[] _ids;
|
||||
|
||||
[Serialize]
|
||||
private int _actorsCount;
|
||||
|
||||
[Serialize]
|
||||
private Guid[] _prefabIds;
|
||||
|
||||
[Serialize]
|
||||
private Guid[] _prefabObjectIds;
|
||||
|
||||
public ReparentAction(Actor actor)
|
||||
: this(new List<Actor> { actor })
|
||||
{
|
||||
}
|
||||
|
||||
public ReparentAction(List<Actor> actors)
|
||||
{
|
||||
var allActors = new List<Actor>(Mathf.NextPowerOfTwo(actors.Count));
|
||||
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
GetAllActors(allActors, actors[i]);
|
||||
}
|
||||
|
||||
var allScripts = new List<Script>(allActors.Capacity);
|
||||
GetAllScripts(allActors, allScripts);
|
||||
|
||||
int allCount = allActors.Count + allScripts.Count;
|
||||
_actorsCount = allActors.Count;
|
||||
_ids = new Guid[allCount];
|
||||
_prefabIds = new Guid[allCount];
|
||||
_prefabObjectIds = new Guid[allCount];
|
||||
|
||||
for (int i = 0; i < allActors.Count; i++)
|
||||
{
|
||||
_ids[i] = allActors[i].ID;
|
||||
_prefabIds[i] = allActors[i].PrefabID;
|
||||
_prefabObjectIds[i] = allActors[i].PrefabObjectID;
|
||||
}
|
||||
|
||||
for (int i = 0; i < allScripts.Count; i++)
|
||||
{
|
||||
int j = _actorsCount + i;
|
||||
_ids[j] = allScripts[i].ID;
|
||||
_prefabIds[j] = allScripts[i].PrefabID;
|
||||
_prefabObjectIds[j] = allScripts[i].PrefabObjectID;
|
||||
}
|
||||
}
|
||||
|
||||
public ReparentAction(Script script)
|
||||
{
|
||||
_actorsCount = 0;
|
||||
_ids = new Guid[] { script.ID };
|
||||
_prefabIds = new Guid[] { script.PrefabID };
|
||||
_prefabObjectIds = new Guid[] { script.PrefabObjectID };
|
||||
}
|
||||
|
||||
private void GetAllActors(List<Actor> allActors, Actor actor)
|
||||
{
|
||||
allActors.Add(actor);
|
||||
|
||||
for (int i = 0; i < actor.ChildrenCount; i++)
|
||||
{
|
||||
var child = actor.GetChild(i);
|
||||
if (!allActors.Contains(child))
|
||||
{
|
||||
GetAllActors(allActors, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetAllScripts(List<Actor> allActors, List<Script> allScripts)
|
||||
{
|
||||
for (int i = 0; i < allActors.Count; i++)
|
||||
{
|
||||
var actor = allActors[i];
|
||||
for (int j = 0; j < actor.ScriptsCount; j++)
|
||||
{
|
||||
allScripts.Add(actor.GetScript(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ActionString => string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Do()
|
||||
{
|
||||
// Note: prefab links are broken by the C++ backend on actor reparenting
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
// Restore links
|
||||
for (int i = 0; i < _actorsCount; i++)
|
||||
{
|
||||
var actor = Object.Find<Actor>(ref _ids[i]);
|
||||
if (actor != null && _prefabIds[i] != Guid.Empty)
|
||||
{
|
||||
Actor.Internal_LinkPrefab(Object.GetUnmanagedPtr(actor), ref _prefabIds[i], ref _prefabObjectIds[i]);
|
||||
}
|
||||
}
|
||||
for (int i = _actorsCount; i < _ids.Length; i++)
|
||||
{
|
||||
var script = Object.Find<Script>(ref _ids[i]);
|
||||
if (script != null && _prefabIds[i] != Guid.Empty)
|
||||
{
|
||||
Script.Internal_LinkPrefab(Object.GetUnmanagedPtr(script), ref _prefabIds[i], ref _prefabObjectIds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_ids = null;
|
||||
_prefabIds = null;
|
||||
_prefabObjectIds = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DragDropEffect OnDragDropHeader(DragData data)
|
||||
{
|
||||
@@ -593,46 +508,24 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
// Drag actors
|
||||
if (_dragActors != null && _dragActors.HasValidDrag)
|
||||
{
|
||||
bool worldPositionLock = Root.GetKey(KeyboardKeys.Control) == false;
|
||||
var singleObject = _dragActors.Objects.Count == 1;
|
||||
if (singleObject)
|
||||
{
|
||||
var targetActor = _dragActors.Objects[0].Actor;
|
||||
var customAction = targetActor.HasPrefabLink ? new ReparentAction(targetActor) : null;
|
||||
using (new UndoBlock(ActorNode.Root.Undo, targetActor, "Change actor parent", customAction))
|
||||
{
|
||||
targetActor.SetParent(newParent, worldPositionLock, true);
|
||||
targetActor.OrderInParent = newOrder;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetActors = _dragActors.Objects.ConvertAll(x => x.Actor);
|
||||
var customAction = targetActors.Any(x => x.HasPrefabLink) ? new ReparentAction(targetActors) : null;
|
||||
using (new UndoMultiBlock(ActorNode.Root.Undo, targetActors, "Change actors parent", customAction))
|
||||
{
|
||||
for (int i = 0; i < targetActors.Count; i++)
|
||||
{
|
||||
var targetActor = targetActors[i];
|
||||
targetActor.SetParent(newParent, worldPositionLock, true);
|
||||
targetActor.OrderInParent = newOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool worldPositionsStays = Root.GetKey(KeyboardKeys.Control) == false;
|
||||
var objects = new SceneObject[_dragActors.Objects.Count];
|
||||
for (int i = 0; i < objects.Length; i++)
|
||||
objects[i] = _dragActors.Objects[i].Actor;
|
||||
var action = new ParentActorsAction(objects, newParent, newOrder, worldPositionsStays);
|
||||
ActorNode.Root.Undo?.AddAction(action);
|
||||
action.Do();
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
// Drag scripts
|
||||
else if (_dragScripts != null && _dragScripts.HasValidDrag)
|
||||
{
|
||||
foreach (var script in _dragScripts.Objects)
|
||||
{
|
||||
var customAction = script.HasPrefabLink ? new ReparentAction(script) : null;
|
||||
using (new UndoBlock(ActorNode.Root.Undo, script, "Change script parent", customAction))
|
||||
{
|
||||
script.SetParent(newParent, true);
|
||||
}
|
||||
}
|
||||
var objects = new SceneObject[_dragScripts.Objects.Count];
|
||||
for (int i = 0; i < objects.Length; i++)
|
||||
objects[i] = _dragScripts.Objects[i];
|
||||
var action = new ParentActorsAction(objects, newParent, newOrder);
|
||||
ActorNode.Root.Undo?.AddAction(action);
|
||||
action.Do();
|
||||
Select();
|
||||
result = DragDropEffect.Move;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,11 @@ namespace FlaxEditor.SceneGraph
|
||||
protected SceneGraphNode(Guid id)
|
||||
{
|
||||
ID = id;
|
||||
SceneGraphFactory.Nodes.Add(id, this);
|
||||
if (SceneGraphFactory.Nodes.TryGetValue(id, out var duplicate) && duplicate != null)
|
||||
{
|
||||
Editor.LogWarning($"Duplicated Scene Graph node with ID {FlaxEngine.Json.JsonSerializer.GetStringID(id)} of type '{duplicate.GetType().FullName}'");
|
||||
}
|
||||
SceneGraphFactory.Nodes[id] = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,18 +98,6 @@ namespace FlaxEditor.SceneGraph
|
||||
/// </summary>
|
||||
public virtual bool CanTransform => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this node can be used for the vertex snapping feature.
|
||||
/// </summary>
|
||||
public bool CanVertexSnap
|
||||
{
|
||||
get
|
||||
{
|
||||
var v = Vector3.Zero;
|
||||
return OnVertexSnap(ref v, out _);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this <see cref="SceneGraphNode"/> is active.
|
||||
/// </summary>
|
||||
@@ -365,14 +357,15 @@ namespace FlaxEditor.SceneGraph
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the vertex snapping of a given point on the object surface that is closest to a given location.
|
||||
/// Performs the vertex snapping for a given ray and hitDistance.
|
||||
/// </summary>
|
||||
/// <param name="point">The position to snap.</param>
|
||||
/// <param name="ray">The ray to raycast.</param>
|
||||
/// <param name="hitDistance">Hit distance from ray to object bounding box.</param>
|
||||
/// <param name="result">The result point on the object mesh that is closest to the specified location.</param>
|
||||
/// <returns>True if got a valid result value, otherwise false (eg. if missing data or not initialized).</returns>
|
||||
public virtual bool OnVertexSnap(ref Vector3 point, out Vector3 result)
|
||||
public virtual bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
|
||||
{
|
||||
result = point;
|
||||
result = Vector3.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,11 +92,12 @@ namespace FlaxEditor.SceneGraph
|
||||
|
||||
private static void FillTree(SceneGraphNode node, List<SceneGraphNode> result)
|
||||
{
|
||||
result.AddRange(node.ChildNodes);
|
||||
for (int i = 0; i < node.ChildNodes.Count; i++)
|
||||
{
|
||||
FillTree(node.ChildNodes[i], result);
|
||||
}
|
||||
if (result.Contains(node))
|
||||
return;
|
||||
result.Add(node);
|
||||
var children = node.ChildNodes;
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
FillTree(children[i], result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,21 +110,9 @@ namespace FlaxEditor.SceneGraph
|
||||
{
|
||||
if (nodes == null || result == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
result.Clear();
|
||||
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
var target = nodes[i];
|
||||
|
||||
// Check if has been already added
|
||||
if (result.Contains(target))
|
||||
continue;
|
||||
|
||||
// Add whole child tree to the results
|
||||
result.Add(target);
|
||||
FillTree(target, result);
|
||||
}
|
||||
FillTree(nodes[i], result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -150,7 +139,6 @@ namespace FlaxEditor.SceneGraph
|
||||
if (node == null || result == null)
|
||||
throw new ArgumentNullException();
|
||||
result.Clear();
|
||||
result.Add(node);
|
||||
FillTree(node, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +602,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
var dataB = (Guid)_node.Values[5 + i * 2];
|
||||
|
||||
pointsAnims[i] = dataB;
|
||||
pointsLocations[i] = new Float2(dataA.X, 0.0f);
|
||||
pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,6 +681,9 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
_animationX.Value = 0.0f;
|
||||
}
|
||||
var ranges = (Float4)Values[0];
|
||||
_animationX.MinValue = ranges.X;
|
||||
_animationX.MaxValue = ranges.Y;
|
||||
_animationXLabel.Enabled = isValid;
|
||||
_animationX.Enabled = isValid;
|
||||
}
|
||||
@@ -732,7 +735,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
var dataB = (Guid)_node.Values[5 + i * 2];
|
||||
|
||||
pointsAnims[i] = dataB;
|
||||
pointsLocations[i] = new Float2(dataA.X, dataA.Y);
|
||||
pointsLocations[i] = new Float2(Mathf.Clamp(dataA.X, rangeX.X, rangeX.Y), Mathf.Clamp(dataA.Y, rangeY.X, rangeY.Y));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,6 +846,11 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
_animationX.Value = 0.0f;
|
||||
_animationY.Value = 0.0f;
|
||||
}
|
||||
var ranges = (Float4)Values[0];
|
||||
_animationX.MinValue = ranges.X;
|
||||
_animationX.MaxValue = ranges.Y;
|
||||
_animationY.MinValue = ranges.Z;
|
||||
_animationY.MaxValue = ranges.W;
|
||||
_animationXLabel.Enabled = isValid;
|
||||
_animationX.Enabled = isValid;
|
||||
_animationYLabel.Enabled = isValid;
|
||||
|
||||
@@ -350,7 +350,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
// Paint Background
|
||||
if (_isSelected)
|
||||
Render2D.DrawRectangle(_textRect, Color.Orange);
|
||||
Render2D.DrawRectangle(_textRect, style.SelectionBorder);
|
||||
|
||||
BackgroundColor = style.BackgroundNormal;
|
||||
var dragAreaColor = BackgroundColor / 2.0f;
|
||||
@@ -1078,7 +1078,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
|
||||
// Paint Background
|
||||
if (_isSelected)
|
||||
Render2D.DrawRectangle(_textRect, Color.Orange);
|
||||
Render2D.DrawRectangle(_textRect, style.SelectionBorder);
|
||||
|
||||
BackgroundColor = style.BackgroundNormal;
|
||||
var dragAreaColor = BackgroundColor / 2.0f;
|
||||
|
||||
@@ -239,6 +239,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
ConnectionsHints = ConnectionsHint.Numeric,
|
||||
IndependentBoxes = new[] { 0, 1, 2 },
|
||||
DependentBoxes = new[] { 3 },
|
||||
SortScore = -1, // Lower sort score to not go above Multiply node
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
1.0f,
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Render2D.DrawSprite(Editor.Instance.Icons.DragBar12, _arrangeButtonRect, _arrangeButtonInUse ? Color.Orange : dragBarColor);
|
||||
if (_arrangeButtonInUse && ArrangeAreaCheck(out _, out var arrangeTargetRect))
|
||||
{
|
||||
Render2D.FillRectangle(arrangeTargetRect, Color.Orange * 0.8f);
|
||||
Render2D.FillRectangle(arrangeTargetRect, style.Selection);
|
||||
}
|
||||
|
||||
// Disabled overlay
|
||||
|
||||
@@ -583,7 +583,7 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
|
||||
// Check if surface has any parameters
|
||||
var parameters = _parametersGetter?.Invoke();
|
||||
int count = parameters?.Count(x => x.IsPublic) ?? 0;
|
||||
int count = parameters?.Count ?? 0;
|
||||
if (count > 0)
|
||||
{
|
||||
// TODO: cache the allocated memory to reduce dynamic allocations
|
||||
@@ -592,28 +592,6 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
var archetypes = new NodeArchetype[count];
|
||||
int archetypeIndex = 0;
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
for (int i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
var param = parameters[i];
|
||||
if (!param.IsPublic)
|
||||
continue;
|
||||
|
||||
var node = (NodeArchetype)_parameterGetNodeArchetype.Clone();
|
||||
node.Title = "Get " + param.Name;
|
||||
node.DefaultValues[0] = param.ID;
|
||||
archetypes[archetypeIndex++] = node;
|
||||
|
||||
if (_parameterSetNodeArchetype != null)
|
||||
{
|
||||
node = (NodeArchetype)_parameterSetNodeArchetype.Clone();
|
||||
node.Title = "Set " + param.Name;
|
||||
node.DefaultValues[0] = param.ID;
|
||||
node.DefaultValues[1] = TypeUtils.GetDefaultValue(param.Type);
|
||||
archetypes[archetypeIndex++] = node;
|
||||
}
|
||||
}
|
||||
|
||||
var groupArchetype = new GroupArchetype
|
||||
{
|
||||
GroupID = 6,
|
||||
@@ -626,26 +604,39 @@ namespace FlaxEditor.Surface.ContextMenu
|
||||
group.ArrowImageOpened = new SpriteBrush(Style.Current.ArrowDown);
|
||||
group.ArrowImageClosed = new SpriteBrush(Style.Current.ArrowRight);
|
||||
group.Close(false);
|
||||
archetypeIndex = 0;
|
||||
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
for (int i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
var param = parameters[i];
|
||||
if (!param.IsPublic)
|
||||
continue;
|
||||
|
||||
var item = new VisjectCMItem(group, groupArchetype, archetypes[archetypeIndex++])
|
||||
// Define Getter node and create CM item
|
||||
var node = (NodeArchetype)_parameterGetNodeArchetype.Clone();
|
||||
node.Title = "Get " + param.Name;
|
||||
node.DefaultValues[0] = param.ID;
|
||||
archetypes[archetypeIndex++] = node;
|
||||
|
||||
var item = new VisjectCMItem(group, groupArchetype, node)
|
||||
{
|
||||
Parent = group
|
||||
};
|
||||
|
||||
// Define Setter node and create CM item if parameter has a setter
|
||||
if (_parameterSetNodeArchetype != null)
|
||||
{
|
||||
item = new VisjectCMItem(group, groupArchetype, archetypes[archetypeIndex++])
|
||||
node = (NodeArchetype)_parameterSetNodeArchetype.Clone();
|
||||
node.Title = "Set " + param.Name;
|
||||
node.DefaultValues[0] = param.ID;
|
||||
node.DefaultValues[1] = TypeUtils.GetDefaultValue(param.Type);
|
||||
archetypes[archetypeIndex++] = node;
|
||||
|
||||
item = new VisjectCMItem(group, groupArchetype, node)
|
||||
{
|
||||
Parent = group
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
group.SortChildren();
|
||||
group.UnlockChildrenRecursive();
|
||||
group.Parent = _groupsPanel;
|
||||
|
||||
@@ -181,7 +181,8 @@ namespace FlaxEditor.Surface
|
||||
if (_isResizing)
|
||||
{
|
||||
// Draw overlay
|
||||
Render2D.FillRectangle(_resizeButtonRect, Color.Orange * 0.3f);
|
||||
Render2D.FillRectangle(_resizeButtonRect, style.Selection);
|
||||
Render2D.DrawRectangle(_resizeButtonRect, style.SelectionBorder);
|
||||
}
|
||||
|
||||
// Resize button
|
||||
|
||||
@@ -96,9 +96,10 @@ namespace FlaxEditor.Surface
|
||||
/// <remarks>Called only when user is selecting nodes using rectangle tool.</remarks>
|
||||
protected virtual void DrawSelection()
|
||||
{
|
||||
var style = FlaxEngine.GUI.Style.Current;
|
||||
var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos);
|
||||
Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f);
|
||||
Render2D.DrawRectangle(selectionRect, Color.Orange);
|
||||
Render2D.FillRectangle(selectionRect, style.Selection);
|
||||
Render2D.DrawRectangle(selectionRect, style.SelectionBorder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -382,6 +382,7 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
new InputActionsContainer.Binding(options => options.Delete, Delete),
|
||||
new InputActionsContainer.Binding(options => options.SelectAll, SelectAll),
|
||||
new InputActionsContainer.Binding(options => options.DeselectAll, DeselectAll),
|
||||
new InputActionsContainer.Binding(options => options.Copy, Copy),
|
||||
new InputActionsContainer.Binding(options => options.Paste, Paste),
|
||||
new InputActionsContainer.Binding(options => options.Cut, Cut),
|
||||
@@ -611,17 +612,14 @@ namespace FlaxEditor.Surface
|
||||
_context.MarkAsModified(graphEdited);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all the nodes.
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
private void BulkSelectUpdate(bool select = true)
|
||||
{
|
||||
bool selectionChanged = false;
|
||||
for (int i = 0; i < _rootControl.Children.Count; i++)
|
||||
{
|
||||
if (_rootControl.Children[i] is SurfaceControl control && !control.IsSelected)
|
||||
if (_rootControl.Children[i] is SurfaceControl control && control.IsSelected != select)
|
||||
{
|
||||
control.IsSelected = true;
|
||||
control.IsSelected = select;
|
||||
selectionChanged = true;
|
||||
}
|
||||
}
|
||||
@@ -629,6 +627,22 @@ namespace FlaxEditor.Surface
|
||||
SelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects all the nodes.
|
||||
/// </summary>
|
||||
public void SelectAll()
|
||||
{
|
||||
BulkSelectUpdate(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deelects all the nodes.
|
||||
/// </summary>
|
||||
public void DeselectAll()
|
||||
{
|
||||
BulkSelectUpdate(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the selection.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,6 +17,30 @@ namespace FlaxEditor.Actions
|
||||
[Serializable]
|
||||
sealed class BreakPrefabLinkAction : IUndoAction
|
||||
{
|
||||
private struct Item
|
||||
{
|
||||
public Guid ID;
|
||||
public Guid PrefabID;
|
||||
public Guid PrefabObjectID;
|
||||
|
||||
public unsafe Item(SceneObject obj, List<Item> nestedPrefabLinks)
|
||||
{
|
||||
ID = obj.ID;
|
||||
PrefabID = obj.PrefabID;
|
||||
PrefabObjectID = obj.PrefabObjectID;
|
||||
if (nestedPrefabLinks != null)
|
||||
{
|
||||
// Check if this object comes from another nested prefab (to break link only from the top-level prefab)
|
||||
Item nested;
|
||||
nested.ID = ID;
|
||||
fixed (Item* i = &this)
|
||||
Editor.Internal_GetPrefabNestedObject(new IntPtr(&i->PrefabID), new IntPtr(&i->PrefabObjectID), new IntPtr(&nested.PrefabID), new IntPtr(&nested.PrefabObjectID));
|
||||
if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty)
|
||||
nestedPrefabLinks.Add(nested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize]
|
||||
private readonly bool _isBreak;
|
||||
|
||||
@@ -24,25 +48,18 @@ namespace FlaxEditor.Actions
|
||||
private Guid _actorId;
|
||||
|
||||
[Serialize]
|
||||
private Guid _prefabId;
|
||||
private List<Item> _items = new();
|
||||
|
||||
[Serialize]
|
||||
private Dictionary<Guid, Guid> _prefabObjectIds;
|
||||
|
||||
private BreakPrefabLinkAction(bool isBreak, Guid actorId, Guid prefabId)
|
||||
private BreakPrefabLinkAction(bool isBreak, Guid actorId)
|
||||
{
|
||||
_isBreak = isBreak;
|
||||
_actorId = actorId;
|
||||
_prefabId = prefabId;
|
||||
}
|
||||
|
||||
private BreakPrefabLinkAction(bool isBreak, Actor actor)
|
||||
{
|
||||
_isBreak = isBreak;
|
||||
_actorId = actor.ID;
|
||||
_prefabId = actor.PrefabID;
|
||||
|
||||
_prefabObjectIds = new Dictionary<Guid, Guid>(1024);
|
||||
CollectIds(actor);
|
||||
}
|
||||
|
||||
@@ -55,7 +72,7 @@ namespace FlaxEditor.Actions
|
||||
{
|
||||
if (actor == null)
|
||||
throw new ArgumentNullException(nameof(actor));
|
||||
return new BreakPrefabLinkAction(true, actor.ID, Guid.Empty);
|
||||
return new BreakPrefabLinkAction(true, actor.ID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -96,53 +113,45 @@ namespace FlaxEditor.Actions
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_prefabObjectIds.Clear();
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
private void DoLink()
|
||||
{
|
||||
if (_prefabObjectIds == null)
|
||||
throw new Exception("Cannot link prefab. Missing objects Ids mapping.");
|
||||
|
||||
var actor = Object.Find<Actor>(ref _actorId);
|
||||
if (actor == null)
|
||||
throw new Exception("Cannot link prefab. Missing actor.");
|
||||
|
||||
// Restore cached links
|
||||
foreach (var e in _prefabObjectIds)
|
||||
{
|
||||
var objId = e.Key;
|
||||
var prefabObjId = e.Value;
|
||||
|
||||
var obj = Object.Find<Object>(ref objId);
|
||||
if (obj is Actor)
|
||||
{
|
||||
Actor.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId);
|
||||
}
|
||||
else if (obj is Script)
|
||||
{
|
||||
Script.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref _prefabId, ref prefabObjId);
|
||||
}
|
||||
}
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
|
||||
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
|
||||
Link(_items);
|
||||
Refresh(actor);
|
||||
}
|
||||
|
||||
private void CollectIds(Actor actor)
|
||||
private void Link(List<Item> items)
|
||||
{
|
||||
_prefabObjectIds.Add(actor.ID, actor.PrefabObjectID);
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
var obj = Object.Find<Object>(ref item.ID);
|
||||
if (obj != null)
|
||||
SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref item.PrefabID, ref item.PrefabObjectID);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectIds(Actor actor, List<Item> nestedPrefabLinks = null)
|
||||
{
|
||||
_items.Add(new Item(actor, nestedPrefabLinks));
|
||||
|
||||
for (int i = 0; i < actor.ChildrenCount; i++)
|
||||
{
|
||||
CollectIds(actor.GetChild(i));
|
||||
}
|
||||
CollectIds(actor.GetChild(i), nestedPrefabLinks);
|
||||
|
||||
for (int i = 0; i < actor.ScriptsCount; i++)
|
||||
{
|
||||
var script = actor.GetScript(i);
|
||||
_prefabObjectIds.Add(script.ID, script.PrefabObjectID);
|
||||
}
|
||||
_items.Add(new Item(actor.GetScript(i), nestedPrefabLinks));
|
||||
}
|
||||
|
||||
private void Refresh(Actor actor)
|
||||
{
|
||||
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
|
||||
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
|
||||
}
|
||||
|
||||
private void DoBreak()
|
||||
@@ -153,18 +162,18 @@ namespace FlaxEditor.Actions
|
||||
if (!actor.HasPrefabLink)
|
||||
throw new Exception("Cannot break missing prefab link.");
|
||||
|
||||
if (_prefabObjectIds == null)
|
||||
_prefabObjectIds = new Dictionary<Guid, Guid>(1024);
|
||||
else
|
||||
_prefabObjectIds.Clear();
|
||||
CollectIds(actor);
|
||||
|
||||
_prefabId = actor.PrefabID;
|
||||
// Cache 'prev' state and extract any nested prefab instances to remain
|
||||
_items.Clear();
|
||||
var nestedPrefabLinks = new List<Item>();
|
||||
CollectIds(actor, nestedPrefabLinks);
|
||||
|
||||
// Break prefab linkage
|
||||
actor.BreakPrefabLink();
|
||||
|
||||
Editor.Instance.Scene.MarkSceneEdited(actor.Scene);
|
||||
Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout();
|
||||
// Restore prefab link for nested instances
|
||||
Link(nestedPrefabLinks);
|
||||
|
||||
Refresh(actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Utilities;
|
||||
|
||||
@@ -25,6 +24,9 @@ namespace FlaxEditor.Actions
|
||||
[Serialize]
|
||||
private Guid[] _nodeParentsIDs;
|
||||
|
||||
[Serialize]
|
||||
private int[] _nodeParentsOrders;
|
||||
|
||||
[Serialize]
|
||||
private Guid[] _prefabIds;
|
||||
|
||||
@@ -43,12 +45,35 @@ namespace FlaxEditor.Actions
|
||||
[Serialize]
|
||||
protected List<SceneGraphNode> _nodeParents;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor.</param>
|
||||
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
|
||||
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
|
||||
internal DeleteActorsAction(Actor actor, bool isInverted = false, bool preserveOrder = true)
|
||||
: this(new List<SceneGraphNode>(1) { SceneGraphFactory.FindNode(actor.ID) }, isInverted, preserveOrder)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="node">The object.</param>
|
||||
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
|
||||
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
|
||||
internal DeleteActorsAction(SceneGraphNode node, bool isInverted = false, bool preserveOrder = true)
|
||||
: this(new List<SceneGraphNode>(1) { node }, isInverted, preserveOrder)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteActorsAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="nodes">The objects.</param>
|
||||
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will be create actors.</param>
|
||||
internal DeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false)
|
||||
/// <param name="isInverted">If set to <c>true</c> action will be inverted - instead of delete it will create actors.</param>
|
||||
/// <param name="preserveOrder">If set to <c>true</c> action will be preserve actors order when performing undo.</param>
|
||||
internal DeleteActorsAction(List<SceneGraphNode> nodes, bool isInverted = false, bool preserveOrder = true)
|
||||
{
|
||||
_isInverted = isInverted;
|
||||
|
||||
@@ -82,6 +107,12 @@ namespace FlaxEditor.Actions
|
||||
_nodeParentsIDs = new Guid[_nodeParents.Count];
|
||||
for (int i = 0; i < _nodeParentsIDs.Length; i++)
|
||||
_nodeParentsIDs[i] = _nodeParents[i].ID;
|
||||
if (preserveOrder)
|
||||
{
|
||||
_nodeParentsOrders = new int[_nodeParents.Count];
|
||||
for (int i = 0; i < _nodeParentsOrders.Length; i++)
|
||||
_nodeParentsOrders[i] = _nodeParents[i].OrderInParent;
|
||||
}
|
||||
|
||||
// Serialize actors
|
||||
_actorsData = Actor.ToBytes(actors.ToArray());
|
||||
@@ -122,6 +153,7 @@ namespace FlaxEditor.Actions
|
||||
{
|
||||
_actorsData = null;
|
||||
_nodeParentsIDs = null;
|
||||
_nodeParentsOrders = null;
|
||||
_prefabIds = null;
|
||||
_prefabObjectIds = null;
|
||||
_nodeParents.Clear();
|
||||
@@ -211,6 +243,8 @@ namespace FlaxEditor.Actions
|
||||
if (foundNode is ActorNode node)
|
||||
{
|
||||
nodes.Add(node);
|
||||
if (_nodeParentsOrders != null)
|
||||
node.Actor.OrderInParent = _nodeParentsOrders[i];
|
||||
}
|
||||
}
|
||||
nodes.BuildNodesParents(_nodeParents);
|
||||
|
||||
199
Source/Editor/Undo/Actions/ParentActorsAction.cs
Normal file
199
Source/Editor/Undo/Actions/ParentActorsAction.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
|
||||
@@ -165,7 +164,7 @@ namespace FlaxEditor.Actions
|
||||
var child = children[j];
|
||||
if (child != actor && child.Name == name)
|
||||
{
|
||||
string newName = Utilities.Utils.IncrementNameNumber(name, x => IsNameValid(x));
|
||||
string newName = Utilities.Utils.IncrementNameNumber(name, IsNameValid);
|
||||
foundNamesResults[newName] = true;
|
||||
actor.Name = newName;
|
||||
// Multiple actors may have the same name, continue
|
||||
|
||||
@@ -38,9 +38,6 @@ namespace FlaxEditor
|
||||
/// <summary>
|
||||
/// Gets the undo operations stack.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The undo operations stack.
|
||||
/// </value>
|
||||
public HistoryStack UndoOperationsStack { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -138,6 +135,7 @@ namespace FlaxEditor
|
||||
return;
|
||||
|
||||
_snapshots.Add(snapshotInstance, new UndoInternal(snapshotInstance, actionString));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -152,9 +150,7 @@ namespace FlaxEditor
|
||||
return;
|
||||
|
||||
if (snapshotInstance == null)
|
||||
{
|
||||
snapshotInstance = _snapshots.Last().Key;
|
||||
}
|
||||
var action = _snapshots[snapshotInstance].End(snapshotInstance);
|
||||
_snapshots.Remove(snapshotInstance);
|
||||
|
||||
@@ -249,9 +245,7 @@ namespace FlaxEditor
|
||||
return;
|
||||
|
||||
if (snapshotInstance == null)
|
||||
{
|
||||
snapshotInstance = (object[])_snapshots.Last().Key;
|
||||
}
|
||||
var action = _snapshots[snapshotInstance].End(snapshotInstance);
|
||||
_snapshots.Remove(snapshotInstance);
|
||||
|
||||
@@ -325,12 +319,10 @@ namespace FlaxEditor
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
UndoOperationsStack.Push(action);
|
||||
|
||||
OnAction(action);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -20,6 +19,7 @@ using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.SceneGraph;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.Json;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Utilities;
|
||||
using FlaxEditor.Windows;
|
||||
@@ -203,6 +203,22 @@ namespace FlaxEditor.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones the value. handles non-value types (such as arrays) that need deep value cloning.
|
||||
/// </summary>
|
||||
/// <param name="value">The source value to clone.</param>
|
||||
/// <returns>The duplicated value.</returns>
|
||||
internal static object CloneValue(object value)
|
||||
{
|
||||
// For objects (eg. arrays) we need to clone them to prevent editing default/reference value within editor
|
||||
if (value != null && (!value.GetType().IsValueType || !value.GetType().IsClass))
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value);
|
||||
value = JsonSerializer.Deserialize(json, value.GetType());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The colors for the keyframes used by the curve editor.
|
||||
/// </summary>
|
||||
@@ -1289,6 +1305,7 @@ namespace FlaxEditor.Utilities
|
||||
inputActions.Add(options => options.Paste, Editor.Instance.SceneEditing.Paste);
|
||||
inputActions.Add(options => options.Duplicate, Editor.Instance.SceneEditing.Duplicate);
|
||||
inputActions.Add(options => options.SelectAll, Editor.Instance.SceneEditing.SelectAllScenes);
|
||||
inputActions.Add(options => options.DeselectAll, Editor.Instance.SceneEditing.DeselectAllScenes);
|
||||
inputActions.Add(options => options.Delete, Editor.Instance.SceneEditing.Delete);
|
||||
inputActions.Add(options => options.Search, () => Editor.Instance.Windows.SceneWin.Search());
|
||||
inputActions.Add(options => options.MoveActorToViewport, Editor.Instance.UI.MoveActorToViewport);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.Gizmo;
|
||||
using FlaxEditor.GUI.ContextMenu;
|
||||
@@ -591,15 +590,32 @@ namespace FlaxEditor.Viewport
|
||||
base.Draw();
|
||||
|
||||
// Selected UI controls outline
|
||||
bool drawAnySelectedControl = false;
|
||||
// TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed)
|
||||
for (var i = 0; i < _window.Selection.Count; i++)
|
||||
{
|
||||
if (_window.Selection[i]?.EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
|
||||
if (_window.Selection[i]?.EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null)
|
||||
{
|
||||
if (!drawAnySelectedControl)
|
||||
{
|
||||
drawAnySelectedControl = true;
|
||||
Render2D.PushTransform(ref _cachedTransform);
|
||||
}
|
||||
var control = controlActor.Control;
|
||||
var bounds = Rectangle.FromPoints(control.PointToParent(this, Float2.Zero), control.PointToParent(this, control.Size));
|
||||
Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize);
|
||||
var bounds = control.EditorBounds;
|
||||
var p1 = control.PointToParent(this, bounds.UpperLeft);
|
||||
var p2 = control.PointToParent(this, bounds.UpperRight);
|
||||
var p3 = control.PointToParent(this, bounds.BottomLeft);
|
||||
var p4 = control.PointToParent(this, bounds.BottomRight);
|
||||
var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4));
|
||||
var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4));
|
||||
bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero));
|
||||
var options = Editor.Instance.Options.Options.Visual;
|
||||
Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize);
|
||||
}
|
||||
}
|
||||
if (drawAnySelectedControl)
|
||||
Render2D.PopTransform();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -799,6 +815,15 @@ namespace FlaxEditor.Viewport
|
||||
{
|
||||
base.OnDebugDraw(context, ref renderContext);
|
||||
|
||||
// Collect selected objects debug shapes again when DebugDraw is active with a custom context
|
||||
_debugDrawData.Clear();
|
||||
var selectedParents = TransformGizmo.SelectedParents;
|
||||
for (int i = 0; i < selectedParents.Count; i++)
|
||||
{
|
||||
if (selectedParents[i].IsActiveInHierarchy)
|
||||
selectedParents[i].OnDebugDraw(_debugDrawData);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)
|
||||
|
||||
@@ -239,6 +239,8 @@ namespace FlaxEditor.Viewport.Previews
|
||||
/// <inheritdoc />
|
||||
public override void OnDestroy()
|
||||
{
|
||||
if (IsDisposing)
|
||||
return;
|
||||
Object.Destroy(ref PreviewLight);
|
||||
Object.Destroy(ref EnvProbe);
|
||||
Object.Destroy(ref Sky);
|
||||
|
||||
@@ -184,6 +184,7 @@ namespace FlaxEditor.Viewport.Previews
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
samplesPerIndex = Math.Max(samplesPerIndex, info.NumChannels);
|
||||
const uint maxSamplesPerIndex = 64;
|
||||
uint samplesPerIndexDiff = Math.Max(1, samplesPerIndex / Math.Min(samplesPerIndex, maxSamplesPerIndex));
|
||||
|
||||
|
||||
@@ -355,6 +355,8 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
try
|
||||
{
|
||||
Editor.Scene.OnSaveStart(_viewport);
|
||||
|
||||
// Simply update changes
|
||||
Editor.Prefabs.ApplyAll(_viewport.Instance);
|
||||
|
||||
@@ -371,6 +373,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Editor.Scene.OnSaveEnd(_viewport);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -170,6 +170,8 @@ namespace FlaxEditor.Windows
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
InputOptions options = FlaxEditor.Editor.Instance.Options.Options.Input;
|
||||
|
||||
// Up
|
||||
if (key == KeyboardKeys.ArrowUp)
|
||||
{
|
||||
@@ -200,7 +202,7 @@ namespace FlaxEditor.Windows
|
||||
Open();
|
||||
}
|
||||
// Ctrl+C
|
||||
else if (key == KeyboardKeys.C && Root.GetKey(KeyboardKeys.Control))
|
||||
else if (options.Copy.Process(this))
|
||||
{
|
||||
Copy();
|
||||
return true;
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace FlaxEditor.Windows
|
||||
private bool _showGUI = true;
|
||||
private bool _showDebugDraw = false;
|
||||
private bool _isMaximized = false, _isUnlockingMouse = false;
|
||||
private bool _isFloating = false, _isBorderless = false;
|
||||
private bool _cursorVisible = true;
|
||||
private float _gameStartTime;
|
||||
private GUI.Docking.DockState _maximizeRestoreDockState;
|
||||
@@ -68,7 +69,7 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether game window is maximized (only in play mode).
|
||||
/// Gets or sets a value indicating whether the game window is maximized (only in play mode).
|
||||
/// </summary>
|
||||
private bool IsMaximized
|
||||
{
|
||||
@@ -78,20 +79,42 @@ namespace FlaxEditor.Windows
|
||||
if (_isMaximized == value)
|
||||
return;
|
||||
_isMaximized = value;
|
||||
if (value)
|
||||
{
|
||||
IsFloating = true;
|
||||
var rootWindow = RootWindow;
|
||||
rootWindow.Maximize();
|
||||
}
|
||||
else
|
||||
{
|
||||
IsFloating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the game window is floating (popup, only in play mode).
|
||||
/// </summary>
|
||||
private bool IsFloating
|
||||
{
|
||||
get => _isFloating;
|
||||
set
|
||||
{
|
||||
if (_isFloating == value)
|
||||
return;
|
||||
_isFloating = value;
|
||||
var rootWindow = RootWindow;
|
||||
if (value)
|
||||
{
|
||||
// Maximize
|
||||
_maximizeRestoreDockTo = _dockedTo;
|
||||
_maximizeRestoreDockState = _dockedTo.TryGetDockState(out _);
|
||||
if (_maximizeRestoreDockState != GUI.Docking.DockState.Float)
|
||||
{
|
||||
var monitorBounds = Platform.GetMonitorBounds(PointToScreen(Size * 0.5f));
|
||||
ShowFloating(monitorBounds.Location + new Float2(200, 200), Float2.Zero, WindowStartPosition.Manual);
|
||||
rootWindow = RootWindow;
|
||||
var size = DefaultSize;
|
||||
var location = monitorBounds.Location + monitorBounds.Size * 0.5f - size * 0.5f;
|
||||
ShowFloating(location, size, WindowStartPosition.Manual);
|
||||
}
|
||||
if (rootWindow != null && !rootWindow.IsMaximized)
|
||||
rootWindow.Maximize();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -105,6 +128,33 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the game window is borderless (only in play mode).
|
||||
/// </summary>
|
||||
private bool IsBorderless
|
||||
{
|
||||
get => _isBorderless;
|
||||
set
|
||||
{
|
||||
if (_isBorderless == value)
|
||||
return;
|
||||
_isBorderless = value;
|
||||
if (value)
|
||||
{
|
||||
IsFloating = true;
|
||||
var rootWindow = RootWindow;
|
||||
var monitorBounds = Platform.GetMonitorBounds(rootWindow.RootWindow.Window.ClientPosition);
|
||||
rootWindow.Window.Position = monitorBounds.Location;
|
||||
rootWindow.Window.SetBorderless(true);
|
||||
rootWindow.Window.ClientSize = monitorBounds.Size;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsFloating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether center mouse position on window focus in play mode. Helps when working with games that lock mouse cursor.
|
||||
/// </summary>
|
||||
@@ -318,6 +368,20 @@ namespace FlaxEditor.Windows
|
||||
InputActions.Add(options => options.TakeScreenshot, () => Screenshot.Capture(string.Empty));
|
||||
InputActions.Add(options => options.DebuggerUnlockMouse, UnlockMouseInPlay);
|
||||
InputActions.Add(options => options.ToggleFullscreen, () => { if (Editor.IsPlayMode) IsMaximized = !IsMaximized; });
|
||||
InputActions.Add(options => options.Play, Editor.Instance.Simulation.DelegatePlayOrStopPlayInEditor);
|
||||
InputActions.Add(options => options.Pause, Editor.Instance.Simulation.RequestResumeOrPause);
|
||||
InputActions.Add(options => options.StepFrame, Editor.Instance.Simulation.RequestPlayOneFrame);
|
||||
InputActions.Add(options => options.ProfilerStartStop, () =>
|
||||
{
|
||||
bool recording = !Editor.Instance.Windows.ProfilerWin.LiveRecording;
|
||||
Editor.Instance.Windows.ProfilerWin.LiveRecording = recording;
|
||||
Editor.Instance.UI.AddStatusMessage($"Profiling {(recording ? "started" : "stopped")}.");
|
||||
});
|
||||
InputActions.Add(options => options.ProfilerClear, () =>
|
||||
{
|
||||
Editor.Instance.Windows.ProfilerWin.Clear();
|
||||
Editor.Instance.UI.AddStatusMessage($"Profiling results cleared.");
|
||||
});
|
||||
}
|
||||
|
||||
private void ChangeViewportRatio(ViewportScaleOptions v)
|
||||
@@ -474,7 +538,9 @@ namespace FlaxEditor.Windows
|
||||
/// <inheritdoc />
|
||||
public override void OnPlayEnd()
|
||||
{
|
||||
IsFloating = false;
|
||||
IsMaximized = false;
|
||||
IsBorderless = false;
|
||||
Cursor = CursorType.Default;
|
||||
}
|
||||
|
||||
@@ -856,7 +922,7 @@ namespace FlaxEditor.Windows
|
||||
var selection = Editor.SceneEditing.Selection;
|
||||
for (var i = 0; i < selection.Count; i++)
|
||||
{
|
||||
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null)
|
||||
if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null && controlActor.Control.VisibleInHierarchy && controlActor.Control.RootWindow != null)
|
||||
{
|
||||
if (!drawAnySelectedControl)
|
||||
{
|
||||
@@ -902,7 +968,7 @@ namespace FlaxEditor.Windows
|
||||
if (animTime < 0)
|
||||
{
|
||||
float alpha = Mathf.Saturate(-animTime / fadeOutTime);
|
||||
Render2D.DrawRectangle(new Rectangle(new Float2(4), Size - 8), Color.Orange * alpha);
|
||||
Render2D.DrawRectangle(new Rectangle(new Float2(4), Size - 8), style.SelectionBorder * alpha);
|
||||
}
|
||||
|
||||
// Add overlay during debugger breakpoint hang
|
||||
@@ -956,6 +1022,29 @@ namespace FlaxEditor.Windows
|
||||
Focus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply the selected window mode to the game window.
|
||||
/// </summary>
|
||||
/// <param name="mode"></param>
|
||||
public void SetWindowMode(InterfaceOptions.GameWindowMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case InterfaceOptions.GameWindowMode.Docked:
|
||||
break;
|
||||
case InterfaceOptions.GameWindowMode.PopupWindow:
|
||||
IsFloating = true;
|
||||
break;
|
||||
case InterfaceOptions.GameWindowMode.MaximizedWindow:
|
||||
IsMaximized = true;
|
||||
break;
|
||||
case InterfaceOptions.GameWindowMode.BorderlessWindow:
|
||||
IsBorderless = true;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes the screenshot of the current viewport.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,6 +12,7 @@ using FlaxEditor.GUI.ContextMenu;
|
||||
using FlaxEditor.GUI.Docking;
|
||||
using FlaxEditor.GUI.Input;
|
||||
using FlaxEditor.GUI.Tree;
|
||||
using FlaxEditor.Options;
|
||||
using FlaxEditor.Scripting;
|
||||
using FlaxEditor.Surface;
|
||||
using FlaxEditor.Windows;
|
||||
@@ -142,6 +143,7 @@ namespace FlaxEngine.Windows.Search
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
InputOptions options = FlaxEditor.Editor.Instance.Options.Options.Input;
|
||||
if (IsFocused)
|
||||
{
|
||||
if (key == KeyboardKeys.Return && Navigate != null)
|
||||
@@ -149,7 +151,7 @@ namespace FlaxEngine.Windows.Search
|
||||
Navigate.Invoke(this);
|
||||
return true;
|
||||
}
|
||||
if (key == KeyboardKeys.C && Root.GetKey(KeyboardKeys.Control))
|
||||
if (options.Copy.Process(this))
|
||||
{
|
||||
Clipboard.Text = Text;
|
||||
return true;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
/// <summary>
|
||||
/// Behavior instance script that runs Behavior Tree execution.
|
||||
/// </summary>
|
||||
API_CLASS() class FLAXENGINE_API Behavior : public Script
|
||||
API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCRIPTING_TYPE(Behavior);
|
||||
|
||||
@@ -706,6 +706,14 @@ bool BehaviorTreeKnowledgeValuesConditionalDecorator::CanUpdate(const BehaviorUp
|
||||
return BehaviorKnowledge::CompareValues((float)ValueA.Get(context.Knowledge), (float)ValueB.Get(context.Knowledge), Comparison);
|
||||
}
|
||||
|
||||
bool BehaviorTreeKnowledgeBooleanDecorator::CanUpdate(const BehaviorUpdateContext& context)
|
||||
{
|
||||
Variant value = Value.Get(context.Knowledge);
|
||||
bool result = (bool)value;
|
||||
result ^= Invert;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BehaviorTreeHasTagDecorator::CanUpdate(const BehaviorUpdateContext& context)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
@@ -445,6 +445,27 @@ public:
|
||||
bool CanUpdate(const BehaviorUpdateContext& context) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks certain knowledge value to conditionally enter the node if the value is set (eg. not-null object reference or boolean value).
|
||||
/// </summary>
|
||||
API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeKnowledgeBooleanDecorator : public BehaviorTreeDecorator
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeKnowledgeBooleanDecorator, BehaviorTreeDecorator);
|
||||
API_AUTO_SERIALIZATION();
|
||||
|
||||
// The value from behavior's knowledge (blackboard, goal or sensor) to check if it's set (eg. not-null object reference or boolean value).
|
||||
API_FIELD(Attributes="EditorOrder(0)")
|
||||
BehaviorKnowledgeSelectorAny Value;
|
||||
|
||||
// If checked, the condition will be inverted.
|
||||
API_FIELD(Attributes="EditorOrder(10)")
|
||||
bool Invert = false;
|
||||
|
||||
public:
|
||||
// [BehaviorTreeNode]
|
||||
bool CanUpdate(const BehaviorUpdateContext& context) override;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if certain actor (from knowledge) has a given tag assigned.
|
||||
/// </summary>
|
||||
|
||||
@@ -900,5 +900,6 @@ private:
|
||||
Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state);
|
||||
void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr);
|
||||
AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr);
|
||||
AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, const AnimGraphNode::StateBaseData& stateData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr);
|
||||
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
|
||||
};
|
||||
|
||||
@@ -563,9 +563,13 @@ void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraph
|
||||
}
|
||||
|
||||
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState)
|
||||
{
|
||||
return UpdateStateTransitions(context, stateMachineData, state->Data.State, state, ignoreState);
|
||||
}
|
||||
|
||||
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, const AnimGraphNode::StateBaseData& stateData, AnimGraphNode* state, AnimGraphNode* ignoreState)
|
||||
{
|
||||
int32 transitionIndex = 0;
|
||||
const AnimGraphNode::StateBaseData& stateData = state->Data.State;
|
||||
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
|
||||
{
|
||||
const uint16 idx = stateData.Transitions[transitionIndex];
|
||||
@@ -640,7 +644,7 @@ AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphCon
|
||||
|
||||
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
|
||||
{
|
||||
AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateMachineBucket.CurrentState);
|
||||
AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateData, stateMachineBucket.CurrentState);
|
||||
if (transition)
|
||||
{
|
||||
InitStateTransition(context, stateMachineBucket, transition);
|
||||
|
||||
@@ -281,10 +281,26 @@ Asset::LoadResult SceneAnimation::load()
|
||||
track.TrackStateIndex = TrackStatesCount++;
|
||||
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
|
||||
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
|
||||
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
|
||||
int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
|
||||
if (trackData->ValueSize == 0)
|
||||
{
|
||||
// When using json data (from non-POD types) read the sum of all keyframes data
|
||||
const int32 keyframesDataStart = stream.GetPosition();
|
||||
for (int32 j = 0; j < trackData->KeyframesCount; j++)
|
||||
{
|
||||
stream.Move<float>(); // Time
|
||||
int32 jsonLen;
|
||||
stream.ReadInt32(&jsonLen);
|
||||
stream.Move(jsonLen);
|
||||
}
|
||||
const int32 keyframesDataEnd = stream.GetPosition();
|
||||
stream.SetPosition(keyframesDataStart);
|
||||
keyframesDataSize = keyframesDataEnd - keyframesDataStart;
|
||||
}
|
||||
trackRuntime->ValueSize = trackData->ValueSize;
|
||||
trackRuntime->KeyframesCount = trackData->KeyframesCount;
|
||||
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
|
||||
trackRuntime->KeyframesSize = keyframesDataSize;
|
||||
needsParent = true;
|
||||
break;
|
||||
}
|
||||
@@ -298,6 +314,7 @@ Asset::LoadResult SceneAnimation::load()
|
||||
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
|
||||
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
|
||||
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3);
|
||||
ASSERT(trackData->ValueSize > 0);
|
||||
trackRuntime->ValueSize = trackData->ValueSize;
|
||||
trackRuntime->KeyframesCount = trackData->KeyframesCount;
|
||||
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
|
||||
@@ -375,6 +392,7 @@ Asset::LoadResult SceneAnimation::load()
|
||||
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
|
||||
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
|
||||
trackRuntime->ValueSize = trackData->ValueSize;
|
||||
ASSERT(trackData->ValueSize > 0);
|
||||
trackRuntime->KeyframesCount = trackData->KeyframesCount;
|
||||
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime));
|
||||
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackData->KeyframesCount);
|
||||
|
||||
@@ -289,6 +289,7 @@ public:
|
||||
/// The keyframes array (items count is KeyframesCount). Each keyframe is represented by pair of time (of type float) and the value data (of size ValueSize).
|
||||
/// </summary>
|
||||
void* Keyframes;
|
||||
int32 KeyframesSize;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Engine/Level/SceneObjectsFactory.h"
|
||||
#include "Engine/Level/Actors/Camera.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Audio/AudioClip.h"
|
||||
#include "Engine/Audio/AudioSource.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MField.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MMethod.h"
|
||||
#include "Engine/Scripting/Internal/ManagedSerialization.h"
|
||||
|
||||
// This could be Update, LateUpdate or FixedUpdate
|
||||
#define UPDATE_POINT Update
|
||||
@@ -370,47 +372,96 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
case SceneAnimation::Track::Types::KeyframesProperty:
|
||||
case SceneAnimation::Track::Types::ObjectReferenceProperty:
|
||||
{
|
||||
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
|
||||
const int32 count = trackDataKeyframes->KeyframesCount;
|
||||
const auto trackRuntime = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
|
||||
const int32 count = trackRuntime->KeyframesCount;
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
// Find the keyframe at time
|
||||
int32 keyframeSize = sizeof(float) + trackDataKeyframes->ValueSize;
|
||||
#define GET_KEY_TIME(idx) *(float*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (idx))
|
||||
const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
|
||||
int32 start = 0;
|
||||
int32 searchLength = count;
|
||||
while (searchLength > 0)
|
||||
// If size is 0 then track uses Json storage for keyframes data (variable memory length of keyframes), otherwise it's optimized simple data with O(1) access
|
||||
if (trackRuntime->ValueSize != 0)
|
||||
{
|
||||
const int32 half = searchLength >> 1;
|
||||
int32 mid = start + half;
|
||||
if (keyTime < GET_KEY_TIME(mid))
|
||||
// Find the keyframe at time (binary search)
|
||||
int32 keyframeSize = sizeof(float) + trackRuntime->ValueSize;
|
||||
#define GET_KEY_TIME(idx) *(float*)((byte*)trackRuntime->Keyframes + keyframeSize * (idx))
|
||||
const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
|
||||
int32 start = 0;
|
||||
int32 searchLength = count;
|
||||
while (searchLength > 0)
|
||||
{
|
||||
searchLength = half;
|
||||
const int32 half = searchLength >> 1;
|
||||
int32 mid = start + half;
|
||||
if (keyTime < GET_KEY_TIME(mid))
|
||||
{
|
||||
searchLength = half;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = mid + 1;
|
||||
searchLength -= half + 1;
|
||||
}
|
||||
}
|
||||
int32 leftKey = Math::Max(0, start - 1);
|
||||
#undef GET_KEY_TIME
|
||||
|
||||
// Return the value
|
||||
void* value = (void*)((byte*)trackRuntime->Keyframes + keyframeSize * (leftKey) + sizeof(float));
|
||||
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
|
||||
{
|
||||
// Object ref track uses Guid for object Id storage
|
||||
Guid id = *(Guid*)value;
|
||||
_objectsMapping.TryGet(id, id);
|
||||
auto obj = Scripting::FindObject<ScriptingObject>(id);
|
||||
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
|
||||
*(void**)target = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = mid + 1;
|
||||
searchLength -= half + 1;
|
||||
// POD memory
|
||||
Platform::MemoryCopy(target, value, trackRuntime->ValueSize);
|
||||
}
|
||||
}
|
||||
int32 leftKey = Math::Max(0, start - 1);
|
||||
#undef GET_KEY_TIME
|
||||
|
||||
// Return the value
|
||||
void* value = (void*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (leftKey) + sizeof(float));
|
||||
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
|
||||
{
|
||||
Guid id = *(Guid*)value;
|
||||
_objectsMapping.TryGet(id, id);
|
||||
auto obj = Scripting::FindObject<ScriptingObject>(id);
|
||||
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
|
||||
*(void**)target = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform::MemoryCopy(target, value, trackDataKeyframes->ValueSize);
|
||||
// Clear pointer
|
||||
*(void**)target = nullptr;
|
||||
|
||||
// Find the keyframe at time (linear search)
|
||||
MemoryReadStream stream((byte*)trackRuntime->Keyframes, trackRuntime->KeyframesSize);
|
||||
int32 prevKeyPos = sizeof(float);
|
||||
int32 jsonLen;
|
||||
for (int32 key = 0; key < count; key++)
|
||||
{
|
||||
float keyTime;
|
||||
stream.ReadFloat(&keyTime);
|
||||
if (keyTime > time)
|
||||
break;
|
||||
prevKeyPos = stream.GetPosition();
|
||||
stream.ReadInt32(&jsonLen);
|
||||
stream.Move(jsonLen);
|
||||
}
|
||||
|
||||
// Read json text
|
||||
stream.SetPosition(prevKeyPos);
|
||||
stream.ReadInt32(&jsonLen);
|
||||
const StringAnsiView json((const char*)stream.GetPositionHandle(), jsonLen);
|
||||
|
||||
// Create empty value of the keyframe type
|
||||
const auto trackData = track.GetData<SceneAnimation::KeyframesPropertyTrack::Data>();
|
||||
const StringAnsiView propertyTypeName(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength);
|
||||
MClass* klass = Scripting::FindClass(propertyTypeName);
|
||||
if (!klass)
|
||||
return false;
|
||||
MObject* obj = MCore::Object::New(klass);
|
||||
if (!obj)
|
||||
return false;
|
||||
if (!klass->IsValueType())
|
||||
MCore::Object::Init(obj);
|
||||
|
||||
// Deserialize value from json
|
||||
ManagedSerialization::Deserialize(json, obj);
|
||||
|
||||
// Set value
|
||||
*(void**)target = obj;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -479,13 +530,13 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
}
|
||||
case SceneAnimation::Track::Types::StringProperty:
|
||||
{
|
||||
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
|
||||
const int32 count = trackDataKeyframes->KeyframesCount;
|
||||
const auto trackRuntime = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
|
||||
const int32 count = trackRuntime->KeyframesCount;
|
||||
if (count == 0)
|
||||
return false;
|
||||
const auto keyframesTimes = (float*)((byte*)trackDataKeyframes + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
|
||||
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackDataKeyframes->KeyframesCount);
|
||||
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackDataKeyframes->KeyframesCount);
|
||||
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
|
||||
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackRuntime->KeyframesCount);
|
||||
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackRuntime->KeyframesCount);
|
||||
|
||||
// Find the keyframe at time
|
||||
#define GET_KEY_TIME(idx) keyframesTimes[idx]
|
||||
@@ -522,7 +573,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
auto& childTrack = anim->Tracks[childTrackIndex];
|
||||
if (childTrack.Disabled || childTrack.ParentIndex != trackIndex)
|
||||
continue;
|
||||
const auto childTrackRuntimeData = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
|
||||
const auto childTrackRuntime = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
|
||||
auto& childTrackState = _tracks[stateIndexOffset + childTrack.TrackStateIndex];
|
||||
|
||||
// Cache field
|
||||
@@ -532,7 +583,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
if (!type)
|
||||
continue;
|
||||
MClass* mclass = MCore::Type::GetClass(type);
|
||||
childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName);
|
||||
childTrackState.Field = mclass->GetField(childTrackRuntime->PropertyName);
|
||||
if (!childTrackState.Field)
|
||||
continue;
|
||||
}
|
||||
@@ -956,7 +1007,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
|
||||
{
|
||||
// Set the value
|
||||
if (MCore::Type::IsPointer(valueType))
|
||||
auto valueTypes = MCore::Type::GetType(valueType);
|
||||
if (valueTypes == MTypes::Object || MCore::Type::IsPointer(valueType))
|
||||
value = (void*)*(intptr*)value;
|
||||
if (state.Property)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ uint64 RawDataAsset::GetMemoryUsage() const
|
||||
Locker.Lock();
|
||||
uint64 result = BinaryAsset::GetMemoryUsage();
|
||||
result += sizeof(RawDataAsset) - sizeof(BinaryAsset);
|
||||
result += Data.Count();
|
||||
result += Data.Capacity();
|
||||
Locker.Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -198,6 +198,12 @@ void ContentService::Dispose()
|
||||
Graphics::DisposeDevice();
|
||||
}
|
||||
|
||||
IAssetFactory::Collection& IAssetFactory::Get()
|
||||
{
|
||||
static Collection Factories(1024);
|
||||
return Factories;
|
||||
}
|
||||
|
||||
AssetsCache* Content::GetRegistry()
|
||||
{
|
||||
return &Cache;
|
||||
|
||||
@@ -20,11 +20,7 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the all registered assets factories. Key is asset typename, value is the factory object.
|
||||
/// </summary>
|
||||
static Collection& Get()
|
||||
{
|
||||
static Collection Factories(1024);
|
||||
return Factories;
|
||||
}
|
||||
static Collection& Get();
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -943,6 +943,11 @@ void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, c
|
||||
DrawLine(origin, origin + direction, color, duration, depthTest);
|
||||
}
|
||||
|
||||
void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest)
|
||||
{
|
||||
DrawLine(origin, origin + direction, color, duration, depthTest);
|
||||
}
|
||||
|
||||
void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float length, float duration, bool depthTest)
|
||||
{
|
||||
if (isnan(length) || isinf(length))
|
||||
|
||||
@@ -87,6 +87,17 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
|
||||
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
|
||||
API_FUNCTION() static void DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the line in a direction.
|
||||
/// [Deprecated in v1.8]
|
||||
/// </summary>
|
||||
/// <param name="origin">The origin of the line.</param>
|
||||
/// <param name="direction">The direction of the line.</param>
|
||||
/// <param name="color">The color.</param>
|
||||
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
|
||||
/// <param name="depthTest">If set to <c>true</c> depth test will be performed, otherwise depth will be ignored.</param>
|
||||
API_FUNCTION() DEPRECATED static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the line in a direction.
|
||||
/// </summary>
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace FlaxEngine
|
||||
/// <summary>
|
||||
/// Virtual input axis binding. Helps with listening for a selected axis input.
|
||||
/// </summary>
|
||||
public class InputAxis
|
||||
public class InputAxis : IComparable, IComparable<InputAxis>
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the axis to use. See <see cref="Input.AxisMappings"/>.
|
||||
@@ -47,13 +47,13 @@ namespace FlaxEngine
|
||||
Input.AxisValueChanged += Handler;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
|
||||
private void Handler(string name)
|
||||
{
|
||||
if (string.Equals(Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
ValueChanged?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="InputAxis"/> class.
|
||||
/// </summary>
|
||||
@@ -61,7 +61,7 @@ namespace FlaxEngine
|
||||
{
|
||||
Input.AxisValueChanged -= Handler;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Releases this object.
|
||||
/// </summary>
|
||||
@@ -70,5 +70,35 @@ namespace FlaxEngine
|
||||
Input.AxisValueChanged -= Handler;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(InputAxis other)
|
||||
{
|
||||
return string.Compare(Name, other.Name, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
return obj is InputAxis other ? CompareTo(other) : -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Name?.GetHashCode() ?? 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is InputAxis other && string.Equals(Name, other.Name, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace FlaxEngine
|
||||
/// <summary>
|
||||
/// Virtual input action binding. Helps with listening for a selected input event.
|
||||
/// </summary>
|
||||
public class InputEvent
|
||||
public class InputEvent : IComparable, IComparable<InputEvent>
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the action to use. See <see cref="Input.ActionMappings"/>.
|
||||
@@ -21,7 +21,7 @@ namespace FlaxEngine
|
||||
public bool Active => Input.GetAction(Name);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the event state. Use Use <see cref="Pressed"/>, <see cref="Pressing"/>, <see cref="Released"/> to catch events without active waiting.
|
||||
/// Returns the event state. Use <see cref="Pressed"/>, <see cref="Pressing"/>, <see cref="Released"/> to catch events without active waiting.
|
||||
/// </summary>
|
||||
public InputActionState State => Input.GetActionState(Name);
|
||||
|
||||
@@ -35,12 +35,12 @@ namespace FlaxEngine
|
||||
/// Occurs when event is pressed (e.g. user pressed a key). Called before scripts update.
|
||||
/// </summary>
|
||||
public event Action Pressed;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when event is being pressing (e.g. user pressing a key). Called before scripts update.
|
||||
/// </summary>
|
||||
public event Action Pressing;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when event is released (e.g. user releases a key). Called before scripts update.
|
||||
/// </summary>
|
||||
@@ -102,5 +102,35 @@ namespace FlaxEngine
|
||||
Input.ActionTriggered -= Handler;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(InputEvent other)
|
||||
{
|
||||
return string.Compare(Name, other.Name, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
return obj is InputEvent other ? CompareTo(other) : -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Name?.GetHashCode() ?? 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is InputEvent other && string.Equals(Name, other.Name, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace FlaxEngine.Interop
|
||||
#endif
|
||||
private static Dictionary<object, ManagedHandle> classAttributesCacheCollectible = new();
|
||||
private static Dictionary<Assembly, ManagedHandle> assemblyHandles = new();
|
||||
private static Dictionary<Type, int> _typeSizeCache = new();
|
||||
private static ConcurrentDictionary<Type, int> _typeSizeCache = new();
|
||||
|
||||
private static Dictionary<string, IntPtr> loadedNativeLibraries = new();
|
||||
internal static Dictionary<string, string> libraryPaths = new();
|
||||
@@ -1594,7 +1594,7 @@ namespace FlaxEngine.Interop
|
||||
private static IntPtr PinValue<T>(T value) where T : struct
|
||||
{
|
||||
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
|
||||
int size = GetTypeSize(typeof(T));
|
||||
int size = TypeHelpers<T>.MarshalSize;
|
||||
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
|
||||
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
|
||||
if (alloc.size < size)
|
||||
@@ -1727,25 +1727,36 @@ namespace FlaxEngine.Interop
|
||||
return tuple;
|
||||
}
|
||||
|
||||
internal static int GetTypeSize(Type type)
|
||||
internal static class TypeHelpers<T>
|
||||
{
|
||||
if (!_typeSizeCache.TryGetValue(type, out var size))
|
||||
public static readonly int MarshalSize;
|
||||
static TypeHelpers()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
try
|
||||
{
|
||||
var marshalType = type;
|
||||
if (type.IsEnum)
|
||||
marshalType = type.GetEnumUnderlyingType();
|
||||
size = Marshal.SizeOf(marshalType);
|
||||
MarshalSize = Marshal.SizeOf(marshalType);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Workaround the issue where structure defined within generic type instance (eg. MyType<int>.MyStruct) fails to get size
|
||||
// https://github.com/dotnet/runtime/issues/46426
|
||||
var obj = Activator.CreateInstance(type);
|
||||
size = Marshal.SizeOf(obj);
|
||||
var obj = RuntimeHelpers.GetUninitializedObject(type);
|
||||
MarshalSize = Marshal.SizeOf(obj);
|
||||
}
|
||||
_typeSizeCache.Add(type, size);
|
||||
}
|
||||
}
|
||||
|
||||
internal static int GetTypeSize(Type type)
|
||||
{
|
||||
if (!_typeSizeCache.TryGetValue(type, out int size))
|
||||
{
|
||||
var marshalSizeField = typeof(TypeHelpers<>).MakeGenericType(type).GetField(nameof(TypeHelpers<int>.MarshalSize), BindingFlags.Static | BindingFlags.Public);
|
||||
size = (int)marshalSizeField.GetValue(null);
|
||||
_typeSizeCache.AddOrUpdate(type, size, (t, v) => size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -1764,14 +1764,6 @@ bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeM
|
||||
}
|
||||
Scripting::ObjectsLookupIdMapping.Set(nullptr);
|
||||
|
||||
// Link objects
|
||||
//for (int32 i = 0; i < objectsCount; i++)
|
||||
{
|
||||
//SceneObject* obj = sceneObjects->At(i);
|
||||
// TODO: post load or post spawn?
|
||||
//obj->PostLoad();
|
||||
}
|
||||
|
||||
// Update objects order
|
||||
//for (int32 i = 0; i < objectsCount; i++)
|
||||
{
|
||||
|
||||
@@ -390,7 +390,7 @@ namespace FlaxEngine
|
||||
}
|
||||
|
||||
#if FLAX_EDITOR
|
||||
internal bool ShowTransform => !(this is UIControl);
|
||||
private bool ShowTransform => !(this is UIControl);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ void SceneObject::BreakPrefabLink()
|
||||
|
||||
String SceneObject::GetNamePath(Char separatorChar) const
|
||||
{
|
||||
Array<String> names;
|
||||
Array<StringView> names;
|
||||
const Actor* a = dynamic_cast<const Actor*>(this);
|
||||
if (!a)
|
||||
a = GetParent();
|
||||
@@ -75,6 +75,8 @@ String SceneObject::GetNamePath(Char separatorChar) const
|
||||
int32 length = names.Count() - 1;
|
||||
for (int32 i = 0; i < names.Count(); i++)
|
||||
length += names[i].Length();
|
||||
if (length == 0)
|
||||
return String::Empty;
|
||||
String result;
|
||||
result.ReserveSpace(length);
|
||||
Char* ptr = result.Get();
|
||||
|
||||
@@ -325,7 +325,7 @@ void SceneObjectsFactory::HandleObjectDeserializationError(const ISerializable::
|
||||
dummyScript->Data = MoveTemp(bufferStr);
|
||||
}
|
||||
#endif
|
||||
LOG(Warning, "Parent actor of the missing object: {0}", parent->GetName());
|
||||
LOG(Warning, "Parent actor of the missing object: '{0}' ({1})", parent->GetNamePath(), String(parent->GetType().Fullname));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "SceneQuery.h"
|
||||
#include "Engine/Scripting/Script.h"
|
||||
#include "Engine/Profiler/Profiler.h"
|
||||
#include "Scripts/MissingScript.h"
|
||||
|
||||
Actor* SceneQuery::RaycastScene(const Ray& ray)
|
||||
{
|
||||
@@ -51,6 +52,15 @@ bool GetAllSerializableSceneObjectsQuery(Actor* actor, Array<SceneObject*>& obje
|
||||
return false;
|
||||
objects.Add(actor);
|
||||
objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count());
|
||||
#if USE_EDITOR
|
||||
// Skip saving Missing Script instances
|
||||
for (int32 i = 0; i < actor->Scripts.Count(); i++)
|
||||
{
|
||||
const int32 idx = objects.Count() - i - 1;
|
||||
if (objects.Get()[idx]->GetTypeHandle() == MissingScript::TypeInitializer)
|
||||
objects.RemoveAtKeepOrder(idx);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ int main(int argc, char* argv[])
|
||||
StringBuilder args;
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
args.Append(argv[i]);
|
||||
String arg;
|
||||
arg.SetUTF8(argv[i], StringUtils::Length(argv[i]));
|
||||
args.Append(arg);
|
||||
|
||||
if (i + 1 != argc)
|
||||
args.Append(TEXT(' '));
|
||||
|
||||
@@ -46,6 +46,7 @@ bool NavCrowd::Init(float maxAgentRadius, int32 maxAgents, NavMeshRuntime* navMe
|
||||
{
|
||||
if (!_crowd || !navMesh)
|
||||
return true;
|
||||
PROFILE_CPU();
|
||||
|
||||
// This can happen on game start when no navmesh is loaded yet (eg. navmesh tiles data is during streaming) so wait for navmesh
|
||||
if (navMesh->GetNavMesh() == nullptr)
|
||||
@@ -145,6 +146,7 @@ void NavCrowd::ResetAgentMove(int32 id)
|
||||
|
||||
void NavCrowd::RemoveAgent(int32 id)
|
||||
{
|
||||
CHECK(id >= 0 && id < _crowd->getAgentCount());
|
||||
_crowd->removeAgent(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// Actor script component that synchronizes the Transform over the network.
|
||||
/// </summary>
|
||||
/// <remarks>Interpolation and prediction logic based on https://www.gabrielgambetta.com/client-server-game-architecture.html.</remarks>
|
||||
API_CLASS(Namespace="FlaxEngine.Networking") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable
|
||||
API_CLASS(Namespace="FlaxEngine.Networking", Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API NetworkTransform : public Script, public INetworkSerializable
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCRIPTING_TYPE(NetworkTransform);
|
||||
|
||||
@@ -3469,7 +3469,8 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
|
||||
Array<PxShape*, InlinedAllocation<8>> shapes;
|
||||
shapes.Resize(actorPhysX->getNbShapes());
|
||||
actorPhysX->getShapes(shapes.Get(), shapes.Count(), 0);
|
||||
const PxTransform centerOfMassOffset = actorPhysX->getCMassLocalPose();
|
||||
PxTransform centerOfMassOffset = actorPhysX->getCMassLocalPose();
|
||||
centerOfMassOffset.q = PxQuat(PxIdentity);
|
||||
|
||||
// Initialize wheels simulation data
|
||||
PxVec3 offsets[PX_MAX_NB_WHEELS];
|
||||
|
||||
@@ -366,7 +366,13 @@ void PlatformBase::Error(const Char* msg)
|
||||
#if PLATFORM_HAS_HEADLESS_MODE
|
||||
if (CommandLine::Options.Headless)
|
||||
{
|
||||
#if PLATFORM_TEXT_IS_CHAR16
|
||||
StringAnsi ansi(msg);
|
||||
ansi += PLATFORM_LINE_TERMINATOR;
|
||||
printf("Error: %s\n", ansi.Get());
|
||||
#else
|
||||
std::cout << "Error: " << msg << std::endl;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
@@ -28,8 +28,8 @@ const DateTime UnixEpoch(1970, 1, 1);
|
||||
|
||||
bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames)
|
||||
{
|
||||
const StringAsANSI<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length());
|
||||
const StringAsANSI<> titleAnsi(*title, title.Length());
|
||||
const StringAsUTF8<> initialDirectoryAnsi(*initialDirectory, initialDirectory.Length());
|
||||
const StringAsUTF8<> titleAnsi(*title, title.Length());
|
||||
const char* initDir = initialDirectory.HasChars() ? initialDirectoryAnsi.Get() : ".";
|
||||
String xdgCurrentDesktop;
|
||||
StringBuilder fileFilter;
|
||||
@@ -113,7 +113,7 @@ bool LinuxFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView&
|
||||
|
||||
bool LinuxFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path)
|
||||
{
|
||||
const StringAsANSI<> titleAnsi(*title, title.Length());
|
||||
const StringAsUTF8<> titleAnsi(*title, title.Length());
|
||||
String xdgCurrentDesktop;
|
||||
Platform::GetEnvironmentVariable(TEXT("XDG_CURRENT_DESKTOP"), xdgCurrentDesktop);
|
||||
|
||||
@@ -158,7 +158,7 @@ bool LinuxFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringV
|
||||
|
||||
bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathAnsi(*path, path.Length());
|
||||
const StringAsUTF8<> pathAnsi(*path, path.Length());
|
||||
char cmd[2048];
|
||||
sprintf(cmd, "xdg-open %s &", pathAnsi.Get());
|
||||
system(cmd);
|
||||
@@ -167,7 +167,7 @@ bool LinuxFileSystem::ShowFileExplorer(const StringView& path)
|
||||
|
||||
bool LinuxFileSystem::CreateDirectory(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathAnsi(*path, path.Length());
|
||||
const StringAsUTF8<> pathAnsi(*path, path.Length());
|
||||
|
||||
// Skip if already exists
|
||||
struct stat fileInfo;
|
||||
@@ -258,7 +258,7 @@ bool DeleteUnixPathTree(const char* path)
|
||||
|
||||
bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (deleteContents)
|
||||
{
|
||||
return DeleteUnixPathTree(pathANSI.Get());
|
||||
@@ -272,7 +272,7 @@ bool LinuxFileSystem::DeleteDirectory(const String& path, bool deleteContents)
|
||||
bool LinuxFileSystem::DirectoryExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISDIR(fileInfo.st_mode);
|
||||
@@ -282,8 +282,8 @@ bool LinuxFileSystem::DirectoryExists(const StringView& path)
|
||||
|
||||
bool LinuxFileSystem::DirectoryGetFiles(Array<String>& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsANSI<> searchPatternANSI(searchPattern);
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> searchPatternANSI(searchPattern);
|
||||
|
||||
// Check if use only top directory
|
||||
if (option == DirectorySearchOption::TopDirectoryOnly)
|
||||
@@ -297,7 +297,7 @@ bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String&
|
||||
DIR* dir;
|
||||
struct stat statPath, statEntry;
|
||||
struct dirent* entry;
|
||||
const StringAsANSI<> pathANSI(*directory, directory.Length());
|
||||
const StringAsUTF8<> pathANSI(*directory, directory.Length());
|
||||
const char* path = pathANSI.Get();
|
||||
|
||||
// Stat for the path
|
||||
@@ -353,7 +353,7 @@ bool LinuxFileSystem::GetChildDirectories(Array<String>& results, const String&
|
||||
bool LinuxFileSystem::FileExists(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
return S_ISREG(fileInfo.st_mode);
|
||||
@@ -363,7 +363,7 @@ bool LinuxFileSystem::FileExists(const StringView& path)
|
||||
|
||||
bool LinuxFileSystem::DeleteFile(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
return unlink(pathANSI.Get()) != 0;
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ uint64 LinuxFileSystem::GetFileSize(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
fileInfo.st_size = 0;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
// Check for directories
|
||||
@@ -385,7 +385,7 @@ uint64 LinuxFileSystem::GetFileSize(const StringView& path)
|
||||
|
||||
bool LinuxFileSystem::IsReadOnly(const StringView& path)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (access(pathANSI.Get(), W_OK) == -1)
|
||||
{
|
||||
return errno == EACCES;
|
||||
@@ -395,7 +395,7 @@ bool LinuxFileSystem::IsReadOnly(const StringView& path)
|
||||
|
||||
bool LinuxFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
|
||||
{
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
struct stat fileInfo;
|
||||
if (stat(pathANSI.Get(), &fileInfo) != -1)
|
||||
{
|
||||
@@ -422,15 +422,15 @@ bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, boo
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
unlink(StringAsANSI<>(*dst, dst.Length()).Get());
|
||||
unlink(StringAsUTF8<>(*dst, dst.Length()).Get());
|
||||
}
|
||||
if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0)
|
||||
if (rename(StringAsUTF8<>(*src, src.Length()).Get(), StringAsUTF8<>(*dst, dst.Length()).Get()) != 0)
|
||||
{
|
||||
if (errno == EXDEV)
|
||||
{
|
||||
if (!CopyFile(dst, src))
|
||||
{
|
||||
unlink(StringAsANSI<>(*src, src.Length()).Get());
|
||||
unlink(StringAsUTF8<>(*src, src.Length()).Get());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -441,8 +441,8 @@ bool LinuxFileSystem::MoveFile(const StringView& dst, const StringView& src, boo
|
||||
|
||||
bool LinuxFileSystem::CopyFile(const StringView& dst, const StringView& src)
|
||||
{
|
||||
const StringAsANSI<> srcANSI(*src, src.Length());
|
||||
const StringAsANSI<> dstANSI(*dst, dst.Length());
|
||||
const StringAsUTF8<> srcANSI(*src, src.Length());
|
||||
const StringAsUTF8<> dstANSI(*dst, dst.Length());
|
||||
|
||||
int srcFile, dstFile;
|
||||
char buffer[4096];
|
||||
@@ -752,7 +752,7 @@ bool LinuxFileSystem::getFilesFromDirectoryAll(Array<String>& results, const cha
|
||||
DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
|
||||
{
|
||||
struct stat fileInfo;
|
||||
const StringAsANSI<> pathANSI(*path, path.Length());
|
||||
const StringAsUTF8<> pathANSI(*path, path.Length());
|
||||
if (stat(pathANSI.Get(), &fileInfo) == -1)
|
||||
{
|
||||
return DateTime::MinValue();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user