255 lines
8.8 KiB
C#
255 lines
8.8 KiB
C#
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using FlaxEditor.Surface.Undo;
|
|
|
|
namespace FlaxEditor.Surface
|
|
{
|
|
public partial class VisjectSurface
|
|
{
|
|
private VisjectSurfaceContext _root;
|
|
private VisjectSurfaceContext _context;
|
|
private readonly Dictionary<ContextHandle, VisjectSurfaceContext> _contextCache = new Dictionary<ContextHandle, VisjectSurfaceContext>();
|
|
|
|
/// <summary>
|
|
/// The surface context stack.
|
|
/// </summary>
|
|
public readonly Stack<VisjectSurfaceContext> ContextStack = new Stack<VisjectSurfaceContext>(8);
|
|
|
|
/// <summary>
|
|
/// Gets the root surface context.
|
|
/// </summary>
|
|
public VisjectSurfaceContext RootContext => _root;
|
|
|
|
/// <summary>
|
|
/// Gets the active surface context.
|
|
/// </summary>
|
|
public VisjectSurfaceContext Context => _context;
|
|
|
|
/// <summary>
|
|
/// Occurs when context gets changed.
|
|
/// </summary>
|
|
public event Action<VisjectSurfaceContext> ContextChanged;
|
|
|
|
/// <summary>
|
|
/// Finds the surface context with the given owning nodes IDs path.
|
|
/// </summary>
|
|
/// <param name="nodePath">The node ids path.</param>
|
|
/// <returns>Found context or null if cannot.</returns>
|
|
public VisjectSurfaceContext FindContext(Span<uint> nodePath)
|
|
{
|
|
// Get size of the path
|
|
int nodePathSize = 0;
|
|
while (nodePathSize < nodePath.Length && nodePath[nodePathSize] != 0)
|
|
nodePathSize++;
|
|
|
|
// Follow each context path to verify if it matches with the path in the input path
|
|
foreach (var e in _contextCache)
|
|
{
|
|
var c = e.Value;
|
|
for (int i = nodePathSize - 1; i >= 0 && c != null; i--)
|
|
c = c.OwnerNodeID == nodePath[i] ? c.Parent : null;
|
|
if (c != null)
|
|
return e.Value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the surface context with the given owning nodes IDs path.
|
|
/// </summary>
|
|
/// <param name="nodePath">The node ids path.</param>
|
|
/// <returns>Found context or null if cannot.</returns>
|
|
public VisjectSurfaceContext OpenContext(Span<uint> nodePath)
|
|
{
|
|
OpenContext(RootContext.Context);
|
|
if (nodePath != null && nodePath.Length != 0)
|
|
{
|
|
for (int i = 0; i < nodePath.Length; i++)
|
|
{
|
|
var node = Context.FindNode(nodePath[i]);
|
|
if (node is ISurfaceContext context)
|
|
OpenContext(context);
|
|
else
|
|
return null;
|
|
}
|
|
}
|
|
return Context;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the Visject surface context for the given surface data source context.
|
|
/// </summary>
|
|
/// <param name="parent">The parent context.</param>
|
|
/// <param name="context">The context.</param>
|
|
/// <returns></returns>
|
|
protected virtual VisjectSurfaceContext CreateContext(VisjectSurfaceContext parent, ISurfaceContext context)
|
|
{
|
|
return new VisjectSurfaceContext(this, parent, context);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the child context of the current context.
|
|
/// </summary>
|
|
/// <param name="context">The context.</param>
|
|
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);
|
|
if (context is SurfaceNode asNode)
|
|
surfaceContext.OwnerNodeID = asNode.ID;
|
|
|
|
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))
|
|
{
|
|
// Go up until the given context
|
|
while (ContextStack.First() != surfaceContext)
|
|
CloseContext();
|
|
return;
|
|
}
|
|
|
|
// Change stack
|
|
ContextStack.Push(surfaceContext);
|
|
|
|
// Update
|
|
OnContextChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Closes the last opened context (the current one).
|
|
/// </summary>
|
|
public void CloseContext()
|
|
{
|
|
if (ContextStack.Count == 0)
|
|
throw new ArgumentException("No context to close.");
|
|
|
|
// Change stack
|
|
ContextStack.Pop();
|
|
|
|
// Update
|
|
OnContextChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the context from the surface and any related cached data.
|
|
/// </summary>
|
|
/// <param name="context">The context.</param>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the current opened context to the given one. Used as a navigation method.
|
|
/// </summary>
|
|
/// <param name="context">The target context.</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when context gets changed. Updates current context and UI. Updates the current context based on the first element in the stack.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|