Editor utility class that temporarily disables selection and context-menu input on a scene view, viewport, and optional tools while a modal operation runs, and restores their previous states on disposal. It also tracks active locks to restore them during editor hotload.
#nullable enable
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
namespace BlenderActions;
/// <summary>Temporarily suppresses viewport selection and context-menu input during modal operations.</summary>
internal sealed class ViewportInputLock : IDisposable
{
/// <summary>Handles new.</summary>
private static readonly HashSet<ViewportInputLock> ActiveLocks = new();
/// <summary>References the scene view bound to the active operation.</summary>
private readonly SceneViewWidget _sceneView;
/// <summary>References the scene viewport bound to the active operation.</summary>
private readonly SceneViewportWidget _viewport;
/// <summary>References the optional tool.</summary>
private readonly EditorTool? _tool;
/// <summary>References the optional sub tool.</summary>
private readonly EditorTool? _subTool;
/// <summary>Tracks whether scene read only.</summary>
private readonly bool _sceneReadOnly;
/// <summary>Tracks whether scene context menu.</summary>
private readonly bool _sceneContextMenu;
/// <summary>Tracks whether viewport read only.</summary>
private readonly bool _viewportReadOnly;
/// <summary>Tracks whether viewport context menu.</summary>
private readonly bool _viewportContextMenu;
/// <summary>Tracks whether tool selection.</summary>
private readonly bool _toolSelection;
/// <summary>Tracks whether tool context menu.</summary>
private readonly bool _toolContextMenu;
/// <summary>Tracks whether sub tool selection.</summary>
private readonly bool _subToolSelection;
/// <summary>Tracks whether sub tool context menu.</summary>
private readonly bool _subToolContextMenu;
/// <summary>Tracks whether this input lock has already restored its state.</summary>
private bool _disposed;
/// <summary>Initializes a new viewport input lock instance.</summary>
public ViewportInputLock(
SceneViewWidget sceneView,
SceneViewportWidget viewport,
EditorTool? tool,
EditorTool? subTool)
{
_sceneView = sceneView;
_viewport = viewport;
_tool = tool;
_subTool = subTool;
_sceneReadOnly = sceneView.ReadOnly;
_sceneContextMenu = sceneView.ContextMenuEnabled;
_viewportReadOnly = viewport.ReadOnly;
_viewportContextMenu = viewport.ContextMenuEnabled;
sceneView.ReadOnly = true;
sceneView.ContextMenuEnabled = false;
viewport.ReadOnly = true;
viewport.ContextMenuEnabled = false;
if(tool != null)
{
_toolSelection = tool.AllowGameObjectSelection;
_toolContextMenu = tool.AllowContextMenu;
tool.AllowGameObjectSelection = false;
tool.AllowContextMenu = false;
}
if(subTool != null && subTool != tool)
{
_subToolSelection = subTool.AllowGameObjectSelection;
_subToolContextMenu = subTool.AllowContextMenu;
subTool.AllowGameObjectSelection = false;
subTool.AllowContextMenu = false;
}
ActiveLocks.Add(this);
}
/// <summary>Restores captured viewport and tool input state exactly once.</summary>
public void Dispose()
{
if(_disposed)
return;
_disposed = true;
ActiveLocks.Remove(this);
Exception? restoreError = null;
Restore(() =>
{
if(_sceneView.IsValid)
{
_sceneView.ReadOnly = _sceneReadOnly;
_sceneView.ContextMenuEnabled = _sceneContextMenu;
}
}, ref restoreError);
Restore(() =>
{
if(_viewport.IsValid)
{
_viewport.ReadOnly = _viewportReadOnly;
_viewport.ContextMenuEnabled = _viewportContextMenu;
}
}, ref restoreError);
Restore(() =>
{
if(_tool != null)
{
_tool.AllowGameObjectSelection = _toolSelection;
_tool.AllowContextMenu = _toolContextMenu;
}
}, ref restoreError);
Restore(() =>
{
if(_subTool != null && _subTool != _tool)
{
_subTool.AllowGameObjectSelection = _subToolSelection;
_subTool.AllowContextMenu = _subToolContextMenu;
}
}, ref restoreError);
if(restoreError != null)
throw restoreError;
}
/// <summary>Restores every active input lock during editor hotload.</summary>
[EditorEvent.Hotload]
private static void RestoreAll()
{
var locks = new ViewportInputLock[ActiveLocks.Count];
ActiveLocks.CopyTo(locks);
foreach(var inputLock in locks)
{
try
{
inputLock.Dispose();
}
catch
{
}
}
ActiveLocks.Clear();
}
/// <summary>Executes one restoration step while retaining the first failure.</summary>
private static void Restore(Action action, ref Exception? firstError)
{
try
{
action();
}
catch(Exception exception)
{
firstError ??= exception;
}
}
}