// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEditor.Surface.Undo; using FlaxEngine; namespace FlaxEditor.Surface { public partial class VisjectSurface { private VisjectSurfaceContext _root; private VisjectSurfaceContext _context; private readonly Dictionary _contextCache = new Dictionary(); /// /// The surface context stack. /// public readonly Stack ContextStack = new Stack(8); /// /// Gets the root surface context. /// public VisjectSurfaceContext RootContext => _root; /// /// Gets the active surface context. /// public VisjectSurfaceContext Context => _context; /// /// Occurs when context gets changed. /// public event Action ContextChanged; /// /// Creates the Visject surface context for the given surface data source context. /// /// The parent context. /// The context. /// protected virtual VisjectSurfaceContext CreateContext(VisjectSurfaceContext parent, ISurfaceContext context) { return new VisjectSurfaceContext(this, parent, context); } /// /// Opens the child context of the current context. /// /// The context. public void OpenContext(ISurfaceContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); if (_context != null && _context.Context == context) return; // Get or create context var contextHandle = new ContextHandle(context); if (!_contextCache.TryGetValue(contextHandle, out VisjectSurfaceContext surfaceContext)) { surfaceContext = CreateContext(_context, context); _context?.Children.Add(surfaceContext); _contextCache.Add(contextHandle, surfaceContext); context.OnContextCreated(surfaceContext); // Load context if (_root != null) { if (surfaceContext.Load()) throw new Exception("Failed to load graph."); } } if (_root == null) _root = surfaceContext; else if (ContextStack.Contains(surfaceContext)) throw new ArgumentException("Context has been already added to the stack."); // Change stack ContextStack.Push(surfaceContext); // Update OnContextChanged(); } /// /// Closes the last opened context (the current one). /// public void CloseContext() { if (ContextStack.Count == 0) throw new ArgumentException("No context to close."); // Change stack ContextStack.Pop(); // Update OnContextChanged(); } /// /// Removes the context from the surface and any related cached data. /// /// The context. public void RemoveContext(ISurfaceContext context) { // Skip if surface is already disposing if (IsDisposing || _isReleasing) return; // Validate input if (context == null) throw new ArgumentNullException(nameof(context)); // Removing root requires to close every context if (RootContext != null && context == RootContext.Context) { while (ContextStack.Count > 0) CloseContext(); } // Check if has context in cache var contextHandle = new ContextHandle(context); if (_contextCache.TryGetValue(contextHandle, out VisjectSurfaceContext surfaceContext)) { // Remove from navigation path while (ContextStack.Contains(surfaceContext)) CloseContext(); // Dispose surfaceContext.Clear(); _contextCache.Remove(contextHandle); } } /// /// Changes the current opened context to the given one. Used as a navigation method. /// /// The target context. public void ChangeContext(ISurfaceContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); if (_context == null) { OpenContext(context); return; } if (_context.Context == context) return; // Check if already in a path var contextHandle = new ContextHandle(context); if (_contextCache.TryGetValue(contextHandle, out VisjectSurfaceContext surfaceContext) && ContextStack.Contains(surfaceContext)) { // Change stack do { ContextStack.Pop(); } while (ContextStack.Peek() != surfaceContext); } else { // TODO: implement this case (need to find first parent of the context that is in path) throw new NotSupportedException("TODO: support changing context to one not in the active path"); } // Update OnContextChanged(); } /// /// Called when context gets changed. Updates current context and UI. Updates the current context based on the first element in the stack. /// protected virtual void OnContextChanged() { var context = ContextStack.Count > 0 ? ContextStack.Peek() : null; _context = context; if (ContextStack.Count == 0) _root = null; // Update root control linkage if (_rootControl != null) { _rootControl.Parent = null; } if (context != null) { _rootControl = _context.RootControl; _rootControl.Parent = this; } else { _rootControl = null; } ContextChanged?.Invoke(_context); } } }