// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEditor.Gizmo;
using FlaxEditor.Content;
using FlaxEditor.GUI.Tree;
using FlaxEditor.GUI.Drag;
using FlaxEditor.SceneGraph;
using FlaxEditor.SceneGraph.GUI;
using FlaxEditor.Scripting;
using FlaxEditor.States;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Windows
{
///
/// Windows used to present loaded scenes collection and whole scene graph.
///
///
public partial class SceneTreeWindow : SceneEditorWindow
{
///
/// The spawnable actors group.
///
public struct ActorsGroup
{
///
/// The group name.
///
public string Name;
///
/// The types to spawn (name and type).
///
public KeyValuePair[] Types;
}
///
/// The Spawnable actors (groups with single entry are inlined without a child menu)
///
public static readonly ActorsGroup[] SpawnActorsGroups =
{
new ActorsGroup
{
Types = new[] { new KeyValuePair("Actor", typeof(EmptyActor)) }
},
new ActorsGroup
{
Types = new[] { new KeyValuePair("Model", typeof(StaticModel)) }
},
new ActorsGroup
{
Types = new[] { new KeyValuePair("Camera", typeof(Camera)) }
},
new ActorsGroup
{
Name = "Lights",
Types = new[]
{
new KeyValuePair("Directional Light", typeof(DirectionalLight)),
new KeyValuePair("Point Light", typeof(PointLight)),
new KeyValuePair("Spot Light", typeof(SpotLight)),
new KeyValuePair("Sky Light", typeof(SkyLight)),
}
},
new ActorsGroup
{
Name = "Visuals",
Types = new[]
{
new KeyValuePair("Environment Probe", typeof(EnvironmentProbe)),
new KeyValuePair("Sky", typeof(Sky)),
new KeyValuePair("Skybox", typeof(Skybox)),
new KeyValuePair("Exponential Height Fog", typeof(ExponentialHeightFog)),
new KeyValuePair("PostFx Volume", typeof(PostFxVolume)),
new KeyValuePair("Decal", typeof(Decal)),
new KeyValuePair("Particle Effect", typeof(ParticleEffect)),
}
},
new ActorsGroup
{
Name = "Physics",
Types = new[]
{
new KeyValuePair("Rigid Body", typeof(RigidBody)),
new KeyValuePair("Character Controller", typeof(CharacterController)),
new KeyValuePair("Box Collider", typeof(BoxCollider)),
new KeyValuePair("Sphere Collider", typeof(SphereCollider)),
new KeyValuePair("Capsule Collider", typeof(CapsuleCollider)),
new KeyValuePair("Mesh Collider", typeof(MeshCollider)),
new KeyValuePair("Fixed Joint", typeof(FixedJoint)),
new KeyValuePair("Distance Joint", typeof(DistanceJoint)),
new KeyValuePair("Slider Joint", typeof(SliderJoint)),
new KeyValuePair("Spherical Joint", typeof(SphericalJoint)),
new KeyValuePair("Hinge Joint", typeof(HingeJoint)),
new KeyValuePair("D6 Joint", typeof(D6Joint)),
}
},
new ActorsGroup
{
Name = "Other",
Types = new[]
{
new KeyValuePair("Animated Model", typeof(AnimatedModel)),
new KeyValuePair("Bone Socket", typeof(BoneSocket)),
new KeyValuePair("CSG Box Brush", typeof(BoxBrush)),
new KeyValuePair("Audio Source", typeof(AudioSource)),
new KeyValuePair("Audio Listener", typeof(AudioListener)),
new KeyValuePair("Scene Animation", typeof(SceneAnimationPlayer)),
new KeyValuePair("Nav Mesh Bounds Volume", typeof(NavMeshBoundsVolume)),
new KeyValuePair("Nav Link", typeof(NavLink)),
new KeyValuePair("Nav Modifier Volume", typeof(NavModifierVolume)),
new KeyValuePair("Spline", typeof(Spline)),
}
},
new ActorsGroup
{
Name = "GUI",
Types = new[]
{
new KeyValuePair("UI Control", typeof(UIControl)),
new KeyValuePair("UI Canvas", typeof(UICanvas)),
new KeyValuePair("Text Render", typeof(TextRender)),
new KeyValuePair("Sprite Render", typeof(SpriteRender)),
}
},
};
private TextBox _searchBox;
private Tree _tree;
private bool _isUpdatingSelection;
private bool _isMouseDown;
private DragAssets _dragAssets;
private DragActorType _dragActorType;
private DragHandlers _dragHandlers;
///
/// Initializes a new instance of the class.
///
/// The editor.
public SceneTreeWindow(Editor editor)
: base(editor, true, ScrollBars.Both)
{
Title = "Scene";
ScrollMargin = new Margin(0, 0, 0, 100.0f);
// Scene searching query input box
var headerPanel = new ContainerControl
{
AnchorPreset = AnchorPresets.HorizontalStretchTop,
IsScrollable = true,
Offsets = new Margin(0, 0, 0, 18 + 6),
Parent = this,
};
_searchBox = new TextBox
{
AnchorPreset = AnchorPresets.HorizontalStretchMiddle,
WatermarkText = "Search...",
Parent = headerPanel,
Bounds = new Rectangle(4, 4, headerPanel.Width - 8, 18),
};
_searchBox.TextChanged += OnSearchBoxTextChanged;
// Create scene structure tree
var root = editor.Scene.Root;
root.TreeNode.ChildrenIndent = 0;
root.TreeNode.Expand();
_tree = new Tree(true)
{
Y = headerPanel.Bottom,
Margin = new Margin(0.0f, 0.0f, -16.0f, 0.0f), // Hide root node
};
_tree.AddChild(root.TreeNode);
_tree.SelectedChanged += Tree_OnSelectedChanged;
_tree.RightClick += Tree_OnRightClick;
_tree.Parent = this;
// Setup input actions
InputActions.Add(options => options.TranslateMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate);
InputActions.Add(options => options.RotateMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate);
InputActions.Add(options => options.ScaleMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale);
InputActions.Add(options => options.FocusSelection, () => Editor.Windows.EditWin.Viewport.FocusSelection());
InputActions.Add(options => options.Rename, Rename);
}
private void OnSearchBoxTextChanged()
{
// Skip events during setup or init stuff
if (IsLayoutLocked)
return;
var root = Editor.Scene.Root;
root.TreeNode.LockChildrenRecursive();
// Update tree
var query = _searchBox.Text;
root.TreeNode.UpdateFilter(query);
root.TreeNode.UnlockChildrenRecursive();
PerformLayout();
PerformLayout();
}
private void Rename()
{
var selection = Editor.SceneEditing.Selection;
if (selection.Count != 0 && selection[0] is ActorNode actor)
{
if (selection.Count != 0)
Editor.SceneEditing.Select(actor);
actor.TreeNode.StartRenaming();
}
}
private void Spawn(Type type)
{
// Create actor
Actor actor = (Actor)FlaxEngine.Object.New(type);
Actor parentActor = null;
if (Editor.SceneEditing.HasSthSelected && Editor.SceneEditing.Selection[0] is ActorNode actorNode)
{
parentActor = actorNode.Actor;
actorNode.TreeNode.Expand();
}
if (parentActor == null)
{
var scenes = Level.Scenes;
if (scenes.Length > 0)
parentActor = scenes[scenes.Length - 1];
}
if (parentActor != null)
{
// Use the same location
actor.Transform = parentActor.Transform;
// Rename actor to identify it easily
actor.Name = StringUtils.IncrementNameNumber(type.Name, x => parentActor.GetChild(x) == null);
}
// Spawn it
Editor.SceneEditing.Spawn(actor, parentActor);
}
///
/// Focuses search box.
///
public void Search()
{
_searchBox.Focus();
}
private void Tree_OnSelectedChanged(List before, List after)
{
// Check if lock events
if (_isUpdatingSelection)
return;
if (after.Count > 0)
{
// Get actors from nodes
var actors = new List(after.Count);
for (int i = 0; i < after.Count; i++)
{
if (after[i] is ActorTreeNode node && node.Actor)
actors.Add(node.ActorNode);
}
// Select
Editor.SceneEditing.Select(actors);
}
else
{
// Deselect
Editor.SceneEditing.Deselect();
}
}
private void Tree_OnRightClick(TreeNode node, Vector2 location)
{
if (!Editor.StateMachine.CurrentState.CanEditScene)
return;
ShowContextMenu(node, location);
}
///
public override void OnInit()
{
Editor.SceneEditing.SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged()
{
_isUpdatingSelection = true;
var selection = Editor.SceneEditing.Selection;
if (selection.Count == 0)
{
_tree.Deselect();
}
else
{
// Find nodes to select
var nodes = new List(selection.Count);
for (int i = 0; i < selection.Count; i++)
{
if (selection[i] is ActorNode node)
{
nodes.Add(node.TreeNode);
}
}
// Select nodes
_tree.Select(nodes);
// For single node selected scroll view so user can see it
if (nodes.Count == 1)
{
nodes[0].ExpandAllParents(true);
ScrollViewTo(nodes[0]);
}
}
_isUpdatingSelection = false;
}
private bool ValidateDragAsset(AssetItem assetItem)
{
return assetItem.OnEditorDrag(this);
}
private static bool ValidateDragActorType(ScriptType actorType)
{
return true;
}
///
public override void Draw()
{
var style = Style.Current;
// Draw overlay
string overlayText = null;
var state = Editor.StateMachine.CurrentState;
var textWrap = TextWrapping.NoWrap;
if (state is LoadingState)
{
overlayText = "Loading...";
}
else if (state is ChangingScenesState)
{
overlayText = "Loading scene...";
}
else if (((ContainerControl)_tree.GetChild(0)).ChildrenCount == 0)
{
overlayText = "No scene\nOpen one from the content window";
textWrap = TextWrapping.WrapWords;
}
if (overlayText != null)
{
Render2D.DrawText(style.FontLarge, overlayText, GetClientArea(), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center, textWrap);
}
base.Draw();
}
///
public override bool OnMouseDown(Vector2 location, MouseButton buttons)
{
if (base.OnMouseDown(location, buttons))
return true;
if (buttons == MouseButton.Right)
{
_isMouseDown = true;
return true;
}
return false;
}
///
public override bool OnMouseUp(Vector2 location, MouseButton buttons)
{
if (base.OnMouseUp(location, buttons))
return true;
if (_isMouseDown && buttons == MouseButton.Right)
{
_isMouseDown = false;
if (Editor.StateMachine.CurrentState.CanEditScene)
{
// Show context menu
Editor.SceneEditing.Deselect();
ShowContextMenu(Parent, location + _searchBox.BottomLeft);
}
return true;
}
return false;
}
///
public override void OnLostFocus()
{
_isMouseDown = false;
base.OnLostFocus();
}
///
public override DragDropEffect OnDragEnter(ref Vector2 location, DragData data)
{
var result = base.OnDragEnter(ref location, data);
if (result == DragDropEffect.None && Editor.StateMachine.CurrentState.CanEditScene)
{
if (_dragHandlers == null)
_dragHandlers = new DragHandlers();
if (_dragAssets == null)
{
_dragAssets = new DragAssets(ValidateDragAsset);
_dragHandlers.Add(_dragAssets);
}
if (_dragAssets.OnDragEnter(data))
return _dragAssets.Effect;
if (_dragActorType == null)
{
_dragActorType = new DragActorType(ValidateDragActorType);
_dragHandlers.Add(_dragActorType);
}
if (_dragActorType.OnDragEnter(data))
return _dragActorType.Effect;
}
return result;
}
///
public override DragDropEffect OnDragMove(ref Vector2 location, DragData data)
{
var result = base.OnDragMove(ref location, data);
if (result == DragDropEffect.None && Editor.StateMachine.CurrentState.CanEditScene && _dragHandlers != null)
{
result = _dragHandlers.Effect;
}
return result;
}
///
public override void OnDragLeave()
{
base.OnDragLeave();
_dragHandlers?.OnDragLeave();
}
///
public override DragDropEffect OnDragDrop(ref Vector2 location, DragData data)
{
var result = base.OnDragDrop(ref location, data);
if (result == DragDropEffect.None)
{
// Drag assets
if (_dragAssets != null && _dragAssets.HasValidDrag)
{
for (int i = 0; i < _dragAssets.Objects.Count; i++)
{
var item = _dragAssets.Objects[i];
var actor = item.OnEditorDrop(this);
actor.Name = item.ShortName;
Level.SpawnActor(actor);
}
result = DragDropEffect.Move;
}
// Drag actor type
else if (_dragActorType != null && _dragActorType.HasValidDrag)
{
for (int i = 0; i < _dragActorType.Objects.Count; i++)
{
var item = _dragActorType.Objects[i];
var actor = item.CreateInstance() as Actor;
if (actor == null)
{
Editor.LogWarning("Failed to spawn actor of type " + item.TypeName);
continue;
}
actor.Name = item.Name;
Level.SpawnActor(actor);
}
result = DragDropEffect.Move;
}
_dragHandlers.OnDragDrop(null);
}
return result;
}
///
public override void OnDestroy()
{
_dragAssets = null;
_dragActorType = null;
_dragHandlers?.Clear();
_dragHandlers = null;
_tree = null;
_searchBox = null;
base.OnDestroy();
}
}
}