// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.Xml; using FlaxEditor.Content; using FlaxEditor.CustomEditors; using FlaxEditor.GUI; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEditor.Surface; using FlaxEditor.Viewport; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; namespace FlaxEditor.Windows.Assets { /// /// Behavior Tree window allows to view and edit asset. /// /// /// public sealed class BehaviorTreeWindow : AssetEditorWindowBase, IVisjectSurfaceWindow, IPresenterOwner { private readonly SplitPanel _split1; private readonly SplitPanel _split2; private CustomEditorPresenter _nodePropertiesEditor; private CustomEditorPresenter _knowledgePropertiesEditor; private BehaviorTreeSurface _surface; private Undo _undo; private readonly ToolStripButton _saveButton; private readonly ToolStripButton _undoButton; private readonly ToolStripButton _redoButton; private bool _showWholeGraphOnLoad = true; private bool _isWaitingForSurfaceLoad; private bool _canEdit = true; /// /// Gets the Visject Surface. /// public BehaviorTreeSurface Surface => _surface; /// /// Gets the undo history context for this window. /// public Undo Undo => _undo; /// /// Current instance of the Behavior Knowledge's blackboard type or null. /// public object Blackboard => _knowledgePropertiesEditor.Selection.Count != 0 ? _knowledgePropertiesEditor.Selection[0] : null; /// public BehaviorTreeWindow(Editor editor, BinaryAssetItem item) : base(editor, item) { var isPlayMode = Editor.IsPlayMode; // Undo _undo = new Undo(); _undo.UndoDone += OnUndoRedo; _undo.RedoDone += OnUndoRedo; _undo.ActionDone += OnUndoAction; // Split Panels _split1 = new SplitPanel(Orientation.Horizontal, ScrollBars.None, ScrollBars.None) { AnchorPreset = AnchorPresets.StretchAll, Offsets = new Margin(0, 0, _toolstrip.Bottom, 0), SplitterValue = 0.7f, Parent = this }; _split2 = new SplitPanel(Orientation.Vertical, ScrollBars.Vertical, ScrollBars.Vertical) { AnchorPreset = AnchorPresets.StretchAll, Offsets = Margin.Zero, SplitterValue = 0.5f, Parent = _split1.Panel2 }; // Surface _surface = new BehaviorTreeSurface(this, Save, _undo) { Parent = _split1.Panel1, Enabled = false }; _surface.SelectionChanged += OnNodeSelectionChanged; // Properties editors _nodePropertiesEditor = new CustomEditorPresenter(null, null, this); // Surface handles undo for nodes editing _nodePropertiesEditor.Features = FeatureFlags.UseDefault; _nodePropertiesEditor.Panel.Parent = _split2.Panel1; _nodePropertiesEditor.Modified += OnNodePropertyEdited; _knowledgePropertiesEditor = new CustomEditorPresenter(null, "No blackboard type assigned", this); // No undo for knowledge editing _knowledgePropertiesEditor.Features = FeatureFlags.None; _knowledgePropertiesEditor.Panel.Parent = _split2.Panel2; // Toolstrip _saveButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Save64, Save).LinkTooltip("Save"); _toolstrip.AddSeparator(); _undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo64, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)"); _redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo64, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)"); _toolstrip.AddSeparator(); _toolstrip.AddButton(Editor.Icons.Search64, Editor.ContentFinding.ShowSearch).LinkTooltip("Open content search tool (Ctrl+F)"); _toolstrip.AddButton(editor.Icons.CenterView64, _surface.ShowWholeGraph).LinkTooltip("Show whole graph"); // Setup input actions InputActions.Add(options => options.Undo, _undo.PerformUndo); InputActions.Add(options => options.Redo, _undo.PerformRedo); InputActions.Add(options => options.Search, Editor.ContentFinding.ShowSearch); SetCanEdit(!isPlayMode); } private void OnUndoRedo(IUndoAction action) { MarkAsEdited(); UpdateToolstrip(); _nodePropertiesEditor.BuildLayoutOnUpdate(); } private void OnUndoAction(IUndoAction action) { MarkAsEdited(); UpdateToolstrip(); } private void OnNodeSelectionChanged() { // Select node instances to view/edit var selection = new List(); var nodes = _surface.Nodes; if (nodes != null) { for (var i = 0; i < nodes.Count; i++) { if (nodes[i] is Surface.Archetypes.BehaviorTree.NodeBase node && node.IsSelected && node.Instance) selection.Add(node.Instance); } } _nodePropertiesEditor.Select(selection); } private void OnNodePropertyEdited() { _surface.MarkAsEdited(); var nodes = _surface.Nodes; for (var i = 0; i < _nodePropertiesEditor.Selection.Count; i++) { if (_nodePropertiesEditor.Selection[i] is BehaviorTreeNode instance) { // Sync instance data with surface node value storage for (var j = 0; j < nodes.Count; j++) { if (nodes[j] is Surface.Archetypes.BehaviorTree.NodeBase node && node.Instance == instance) { node._isValueEditing = true; node.SetValue(1, FlaxEngine.Json.JsonSerializer.SaveToBytes(instance)); node._isValueEditing = false; break; } } } } } private void UpdateKnowledge() { var rootNode = _surface.FindNode(19, 2) as Surface.Archetypes.BehaviorTree.Node; if (rootNode != null) rootNode.ValuesChanged += UpdateKnowledge; var rootInstance = rootNode?.Instance as BehaviorTreeRootNode; var blackboardType = TypeUtils.GetType(rootInstance?.BlackboardType); if (blackboardType) { var blackboardInstance = blackboardType.CreateInstance(); _knowledgePropertiesEditor.Select(blackboardInstance); } else { _knowledgePropertiesEditor.Deselect(); } } /// public override void Save() { // Early check if (!IsEdited || _asset == null || _isWaitingForSurfaceLoad) return; // Check if surface has been edited if (_surface.IsEdited) { if (SaveSurface()) return; } ClearEditedFlag(); OnSurfaceEditedChanged(); _item.RefreshThumbnail(); } /// protected override void UpdateToolstrip() { _saveButton.Enabled = _canEdit && IsEdited; _undoButton.Enabled = _canEdit && _undo.CanUndo; _redoButton.Enabled = _canEdit && _undo.CanRedo; base.UpdateToolstrip(); } /// protected override void OnAssetLinked() { _isWaitingForSurfaceLoad = true; base.OnAssetLinked(); } /// /// Focuses the node. /// /// The node. public void ShowNode(SurfaceNode node) { SelectTab(); RootWindow.Focus(); Surface.Focus(); Surface.FocusNode(node); } /// public Asset SurfaceAsset => Asset; /// public string SurfaceName => "Behavior Tree"; /// public byte[] SurfaceData { get => _asset.LoadSurface(); set { // Save data to the asset if (_asset.SaveSurface(value)) { _surface.MarkAsEdited(); Editor.LogError("Failed to save surface data"); } _asset.Reload(); } } /// public VisjectSurfaceContext ParentContext => null; /// public void OnContextCreated(VisjectSurfaceContext context) { } /// public void OnSurfaceEditedChanged() { if (_surface.IsEdited) MarkAsEdited(); } /// public void OnSurfaceGraphEdited() { } /// public void OnSurfaceClose() { Close(); } /// protected override void UnlinkItem() { _isWaitingForSurfaceLoad = false; base.UnlinkItem(); } private bool LoadSurface() { if (_surface.Load()) { Editor.LogError("Failed to load Behavior Tree surface."); return true; } return false; } private bool SaveSurface() { _surface.Save(); return false; } private void SetCanEdit(bool canEdit) { if (_canEdit == canEdit) return; _canEdit = canEdit; _undo.Enabled = canEdit; _surface.CanEdit = canEdit; _nodePropertiesEditor.ReadOnly = !_canEdit; _knowledgePropertiesEditor.ReadOnly = true; UpdateToolstrip(); } /// public override void OnPlayBegin() { base.OnPlayBegin(); SetCanEdit(false); } /// public override void OnPlayEnd() { SetCanEdit(true); base.OnPlayEnd(); } /// public override void Update(float deltaTime) { base.Update(deltaTime); if (_isWaitingForSurfaceLoad && _asset.IsLoaded) { _isWaitingForSurfaceLoad = false; if (LoadSurface()) { Close(); return; } // Setup _undo.Clear(); _surface.Enabled = true; _nodePropertiesEditor.BuildLayout(); _knowledgePropertiesEditor.BuildLayout(); ClearEditedFlag(); if (_showWholeGraphOnLoad) { _showWholeGraphOnLoad = false; _surface.ShowWholeGraph(); } SurfaceLoaded?.Invoke(); _knowledgePropertiesEditor.ReadOnly = true; UpdateKnowledge(); } } /// public override bool UseLayoutData => true; /// public override void OnLayoutSerialize(XmlWriter writer) { LayoutSerializeSplitter(writer, "Split1", _split1); LayoutSerializeSplitter(writer, "Split2", _split1); } /// public override void OnLayoutDeserialize(XmlElement node) { LayoutDeserializeSplitter(node, "Split1", _split1); LayoutDeserializeSplitter(node, "Split2", _split2); } /// public override void OnLayoutDeserialize() { _split1.SplitterValue = 0.7f; _split2.SplitterValue = 0.5f; } /// public override void OnDestroy() { if (IsDisposing) return; _undo.Enabled = false; _nodePropertiesEditor.Deselect(); _knowledgePropertiesEditor.Deselect(); _undo.Clear(); base.OnDestroy(); } /// public IEnumerable NewParameterTypes => Editor.CodeEditing.VisualScriptPropertyTypes.Get(); /// public event Action SurfaceLoaded; /// public void OnParamRenameUndo() { } /// public void OnParamEditAttributesUndo() { } /// public void OnParamAddUndo() { } /// public void OnParamRemoveUndo() { } /// public object GetParameter(int index) { throw new NotSupportedException(); } /// public void SetParameter(int index, object value) { throw new NotSupportedException(); } /// public Asset VisjectAsset => Asset; /// public VisjectSurface VisjectSurface => _surface; /// public EditorViewport PresenterViewport => null; /// public void Select(List nodes) { } } }