Files
2026-07-13 13:13:17 +08:00

596 lines
31 KiB
C#

using System.IO;
using ImGuiNET;
using T3.Core.DataTypes;
using T3.Core.Operator;
using T3.Core.Resource;
using T3.Core.Resource.Assets;
using T3.Core.SystemUi;
using T3.Editor.Gui.Graph.Dialogs;
using T3.Editor.Gui.Input;
using T3.Editor.Gui.Interaction;
using T3.Editor.Gui.Interaction.Keyboard;
using T3.Editor.Gui.Interaction.Variations;
using T3.Editor.Gui.MagGraph.States;
using T3.Editor.Gui.OutputUi;
using T3.Editor.Gui.Styling;
using T3.Editor.Gui.UiHelpers.Thumbnails;
using T3.Editor.Gui.Windows.RenderExport;
using T3.Editor.UiModel;
using T3.Editor.UiModel.Commands;
using T3.Editor.UiModel.Commands.Graph;
using T3.Editor.UiModel.Exporting;
using T3.Editor.UiModel.Helpers;
using T3.Editor.UiModel.InputsAndTypes;
using T3.Editor.UiModel.Modification;
using T3.Editor.UiModel.ProjectHandling;
using T3.Editor.UiModel.Selection;
using MagGraphView = T3.Editor.Gui.MagGraph.Ui.MagGraphView;
namespace T3.Editor.Gui.MagGraph.Interaction;
internal static class GraphContextMenu
{
// Stable ids for CustomComponents.DrawMenuItem. Enum casts are compile-time constants, so they
// survive hot reload (unlike static-field id initializers, which don't re-run and collapse to 0).
private enum MenuItemIds
{
Undo = 1,
SelectMenu, SelectCustomOps, SelectConnected, SelectConnectedInputs,
Disabled, Bypassed, ControlSnapshots, Rename, AddComment, AlignLeft,
DisplayMenu, DisplaySmall, DisplayResizable, DisplayExpanded, DisplayBackground, DisplayPin,
GenerateProxy,
Copy, Duplicate, Paste, Delete, RenameOutput,
MoreMenu, CopyKeyframes, CopySymbolName, CopyDependencies, DuplicateConnected, PasteValues,
OpenFolderMenu, OpenProjectFolder, OpenResourcesFolder,
AddNode, AddSection, AddInput, AddOutput,
SymbolDefMenu, RenameSymbol, DuplicateAsNewType, CombineIntoNewType, SetThumbnail,
}
internal static void DrawContextMenuContent(GraphUiContext context, ProjectView projectView)
{
const CustomComponents.ButtonStates emphasized = CustomComponents.ButtonStates.Emphasized;
const CustomComponents.ButtonStates muted = CustomComponents.ButtonStates.Default;
var compositionSymbolUi = context.CompositionInstance.GetSymbolUi();
var nodeSelection = context.Selector;
var selectedChildUis = nodeSelection.GetSelectedChildUis().ToList();
var oneOpSelected = selectedChildUis.Count == 1;
var someOpsSelected = selectedChildUis.Count > 0;
var canModify = !compositionSymbolUi.Symbol.SymbolPackage.IsReadOnly;
var isSaving = T3Ui.IsCurrentlySaving;
// ----- Undo --------------------------------------------------------
var nextUndoTitle = UndoRedoStack.CanUndo ? $" ({UndoRedoStack.GetNextUndoTitle()})" : string.Empty;
if (CustomComponents.DrawMenuItem((int)MenuItemIds.Undo, Icon.None, "Undo" + nextUndoTitle,
UserActions.Undo.ListShortcuts(), isChecked: false, isEnabled: UndoRedoStack.CanUndo,
reserveIconColumn: false, state: muted))
{
UndoRedoStack.Undo();
}
// ----- Selection state, toggles & per-op actions -------------------
// Hidden entirely (not greyed) when nothing is selected — these only make sense for a selection.
if (someOpsSelected)
{
CustomComponents.SeparatorLine();
var selectionLabel = oneOpSelected
? $"{selectedChildUis[0].SymbolChild.ReadableName}..."
: $"{selectedChildUis.Count} selected items...";
CustomComponents.DrawMenuGroupLabel(selectionLabel);
var allSelectedDisabled = selectedChildUis.TrueForAll(c => c.SymbolChild.IsDisabled);
if (CustomComponents.DrawMenuItem((int)MenuItemIds.Disabled, Icon.None, "Disabled",
UserActions.ToggleDisabled.ListShortcuts(), isChecked: allSelectedDisabled,
reserveIconColumn: false, state: allSelectedDisabled ? emphasized : muted))
{
NodeActions.ToggleDisabledForSelectedElements(nodeSelection);
}
var allSelectedBypassed = selectedChildUis.TrueForAll(c => c.SymbolChild.IsBypassed);
if (CustomComponents.DrawMenuItem((int)MenuItemIds.Bypassed, Icon.None, "Bypassed",
UserActions.ToggleBypassed.ListShortcuts(), isChecked: allSelectedBypassed,
reserveIconColumn: false, state: allSelectedBypassed ? emphasized : muted))
{
NodeActions.ToggleBypassedForSelectedElements(nodeSelection);
}
if (canModify)
{
var snapshotsEnabled = selectedChildUis.Any(c => c.EnabledForSnapshots);
if (CustomComponents.DrawMenuItem((int)MenuItemIds.ControlSnapshots, Icon.None, "Control with Snapshots",
UserActions.ToggleSnapshotControl.ListShortcuts(), isChecked: snapshotsEnabled,
reserveIconColumn: false, state: snapshotsEnabled ? emphasized : muted))
{
ToggleSnapshotControlForSelection(context, compositionSymbolUi, selectedChildUis);
}
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.Rename, Icon.None, "Rename",
isChecked: false, isEnabled: oneOpSelected, reserveIconColumn: false, state: muted))
{
RenamingOperator.OpenForChildUi(selectedChildUis[0]);
context.StateMachine.SetState(GraphStates.RenameChild, context);
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.AddComment, Icon.None, "Add Comment",
UserActions.AddComment.ListShortcuts(), isChecked: false, isEnabled: oneOpSelected,
reserveIconColumn: false, state: muted))
{
context.EditCommentDialog.ShowNextFrame();
}
var canAlign = context.StateMachine.CurrentState == GraphStates.Default && selectedChildUis.Count > 1;
if (CustomComponents.DrawMenuItem((int)MenuItemIds.AlignLeft, Icon.None, "Align select left",
UserActions.AlignSelectionLeft.ListShortcuts(), isChecked: false, isEnabled: canAlign,
reserveIconColumn: false, state: muted))
{
Modifications.AlignSelectionToLeft(context);
}
DrawDisplayAsSubMenu(context, nodeSelection, selectedChildUis, oneOpSelected, someOpsSelected);
}
CustomComponents.SeparatorLine();
// Top-level "Generate proxy" for a selected op whose file-path input points at a video.
if (oneOpSelected
&& context.CompositionInstance.Children.TryGetChildInstance(selectedChildUis[0].Id, out var proxyOpInstance)
&& TryGetVideoProxySource(proxyOpInstance, out var proxySource))
{
var proxyState = ProxyGenerationService.Jobs.TryGetValue(proxySource, out var status)
? status.State
: (ProxyGenerationService.JobState?)null;
var (proxyLabel, proxyBusy) = proxyState switch
{
ProxyGenerationService.JobState.Queued => ("Proxy queued…", true),
ProxyGenerationService.JobState.Generating => ($"Generating proxy… {status.Progress * 100:0}%", true),
ProxyGenerationService.JobState.Done => ("Regenerate proxy", false),
_ => ("Generate proxy video", false),
};
// Block generation (but not the busy/queued states) when the target drive is low on space.
var hasDisk = ProxyGenerationService.HasEnoughFreeDisk(proxySource, out var proxyFreeBytes, out var proxyReqBytes);
if (CustomComponents.DrawMenuItem((int)MenuItemIds.GenerateProxy, Icon.None, proxyLabel,
isChecked: false, isEnabled: !proxyBusy && hasDisk, reserveIconColumn: false, state: muted))
{
ProxyGenerationService.Enqueue(proxySource);
}
if (!proxyBusy && !hasDisk)
CustomComponents.TooltipForLastItem(
$"Not enough free disk to generate a proxy ({proxyFreeBytes / 1e9:0.#} GB free, {proxyReqBytes / 1e9:0.#} GB required).");
CustomComponents.SeparatorLine();
}
// ----- Select / clipboard / duplication ----------------------------
if (CustomComponents.DrawSubMenu((int)MenuItemIds.SelectMenu, "Select..."))
{
if (CustomComponents.DrawMenuItem((int)MenuItemIds.SelectCustomOps, Icon.None, "Custom Operators",
reserveCheckmarkColumn: false, reserveIconColumn: false))
{
var foundAny = false;
var childUis = selectedChildUis.Count > 0
? selectedChildUis.ToList()
: context.CompositionInstance.Children.Values.Select(c => c.GetChildUi());
if (selectedChildUis.Count > 0)
nodeSelection.Clear();
foreach (var item in childUis)
{
if (item == null)
continue;
if (SymbolAnalysis.TryGetOperatorType(item.SymbolChild.Symbol, out var type)
&& type != SymbolAnalysis.OperatorClassification.Unknown)
{
continue;
}
if (context.CompositionInstance.Children.TryGetChildInstance(item.Id, out var instance))
{
nodeSelection.AddSelection(item, instance);
}
foundAny = true;
}
if (foundAny)
{
context.ProjectView.FocusViewToSelection();
}
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.SelectConnected, Icon.None, "Select connected",
isEnabled: someOpsSelected, reserveCheckmarkColumn: false, reserveIconColumn: false))
{
var connectedIds = new HashSet<Guid>();
var selectedChildren = new List<Symbol.Child>();
foreach (var child in selectedChildUis)
{
selectedChildren.Add(child.SymbolChild);
}
Structure.CollectConnectedChildIds(context.CompositionInstance.Symbol, selectedChildren, connectedIds);
if (connectedIds.Count > 0)
{
context.Selector.Clear();
foreach (var id in connectedIds)
{
if (context.CompositionInstance.Children.TryGetChildInstance(id, out var instance))
{
var node = instance.GetChildUi();
if (node != null)
{
context.Selector.AddSelection(node, instance);
}
}
}
}
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.SelectConnectedInputs, Icon.None, "Select connected inputs",
isEnabled: someOpsSelected, reserveCheckmarkColumn: false, reserveIconColumn: false))
{
var connectedIds = new HashSet<Guid>();
foreach (var child in selectedChildUis)
{
Structure.CollectConnectedChildren(child.SymbolChild,
context.CompositionInstance.Symbol,
connectedIds);
}
if (connectedIds.Count > 0)
{
context.Selector.Clear();
foreach (var id in connectedIds)
{
if (context.CompositionInstance.Children.TryGetChildInstance(id, out var instance))
{
var node = instance.GetChildUi();
if (node != null)
{
context.Selector.AddSelection(node, instance);
}
}
}
}
}
ImGui.EndMenu();
}
// ----- Clipboard / duplication --------------------------------------
// Copy / Duplicate / Paste stay flat so they're one scan away with their shortcut on display;
// the rarer variants live under "more...".
if (CustomComponents.DrawMenuItem((int)MenuItemIds.Copy, Icon.None, "Copy",
UserActions.CopyToClipboard.ListShortcuts(), isChecked: false, isEnabled: someOpsSelected,
reserveIconColumn: false, state: muted))
{
NodeActions.CopySelectedNodesToClipboard(nodeSelection, context.CompositionInstance);
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.Duplicate, Icon.None, "Duplicate",
UserActions.Duplicate.ListShortcuts(), isChecked: false, isEnabled: someOpsSelected && !isSaving,
reserveIconColumn: false, state: muted))
{
NodeActions.CopySelectedNodesToClipboard(nodeSelection, context.CompositionInstance);
NodeActions.PasteClipboard(nodeSelection, context.View, context.CompositionInstance);
context.Layout.FlagStructureAsChanged();
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.Paste, Icon.None, "Paste",
UserActions.PasteFromClipboard.ListShortcuts(), reserveIconColumn: false, state: muted))
{
NodeActions.PasteClipboard(nodeSelection, context.View, context.CompositionInstance);
context.Layout.FlagStructureAsChanged();
}
var selectedInputUis = nodeSelection.GetSelectedNodes<IInputUi>().ToList();
var selectedOutputUis = nodeSelection.GetSelectedNodes<IOutputUi>().ToList();
var canDelete = (someOpsSelected || selectedInputUis.Count > 0 || selectedOutputUis.Count > 0) && !isSaving;
if (CustomComponents.DrawMenuItem((int)MenuItemIds.Delete, Icon.None, "Delete",
"Del", isChecked: false, isEnabled: canDelete, reserveIconColumn: false, state: muted))
{
NodeActions.DeleteSelectedElements(nodeSelection, compositionSymbolUi, selectedChildUis, selectedInputUis, selectedOutputUis);
context.Layout.FlagStructureAsChanged();
}
DrawEditMoreSubMenu(context, nodeSelection, selectedChildUis, oneOpSelected, someOpsSelected, isSaving);
if (canModify && selectedOutputUis.Count == 1
&& CustomComponents.DrawMenuItem((int)MenuItemIds.RenameOutput, Icon.None, "Rename output",
reserveIconColumn: false, state: muted))
{
context.RenameOutputDialog.ShowNextFrame(compositionSymbolUi.Symbol, selectedOutputUis[0].Id);
}
CustomComponents.SeparatorLine();
// ----- Add ----------------------------------------------------------
CustomComponents.DrawMenuGroupLabel("Add...");
if (CustomComponents.DrawMenuItem((int)MenuItemIds.AddNode, Icon.None, "Operator",
"TAB", reserveIconColumn: false, state: muted))
{
var posOnCanvas = context.View.InverseTransformPositionFloat(CustomComponents.ScreenPosOnOpeningContextMenu);
context.Placeholder.OpenOnCanvas(context, posOnCanvas);
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.AddSection, Icon.None, "Section",
UserActions.AddSection.ListShortcuts(), reserveIconColumn: false, state: muted))
{
var newSection = NodeActions.AddSection(nodeSelection, context.View, context.CompositionInstance, CustomComponents.ScreenPosOnOpeningContextMenu);
context.ActiveSectionId = newSection.Id;
context.StateMachine.SetState(GraphStates.RenameSection, context);
}
if (canModify)
{
if (CustomComponents.DrawMenuItem((int)MenuItemIds.AddInput, Icon.None, "Input Parameter",
reserveIconColumn: false, state: muted))
{
var posOnCanvas = context.View.InverseTransformPositionFloat(CustomComponents.ScreenPosOnOpeningContextMenu);
context.AddInputDialog.ShowNextFrame(posOnCanvas);
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.AddOutput, Icon.None, "Output",
reserveIconColumn: false, state: muted))
{
var posOnCanvas = context.View.InverseTransformPositionFloat(CustomComponents.ScreenPosOnOpeningContextMenu);
context.AddOutputDialog.ShowNextFrame(posOnCanvas);
}
}
// ----- Symbol definition --------------------------------------------
if (projectView.GraphView is MagGraphView)
{
CustomComponents.SeparatorLine();
DrawSymbolDefinitionSubMenu(context, projectView, selectedChildUis, oneOpSelected, someOpsSelected, isSaving);
}
// The scroll-canvas popup uses a tighter window padding than the app menu, so the last row sits
// flush against the bottom edge; nudge a little breathing room in.
FormInputs.AddVerticalSpace(2);
}
private static void DrawDisplayAsSubMenu(GraphUiContext context, NodeSelection nodeSelection, List<SymbolUi.Child> selectedChildUis,
bool oneOpSelected, bool someOpsSelected)
{
const CustomComponents.ButtonStates emphasized = CustomComponents.ButtonStates.Emphasized;
const CustomComponents.ButtonStates muted = CustomComponents.ButtonStates.Default;
if (!CustomComponents.DrawSubMenu((int)MenuItemIds.DisplayMenu, "Display..."))
return;
var isSmall = selectedChildUis.Any(child => child.Style == SymbolUi.Child.Styles.Default);
if (CustomComponents.DrawMenuItem((int)MenuItemIds.DisplaySmall, Icon.None, "Small",
isChecked: isSmall, isEnabled: someOpsSelected, reserveIconColumn: false,
state: isSmall ? emphasized : muted))
{
foreach (var childUi in selectedChildUis)
{
childUi.Style = SymbolUi.Child.Styles.Default;
}
}
var isResizable = selectedChildUis.Any(child => child.Style == SymbolUi.Child.Styles.Resizable);
if (CustomComponents.DrawMenuItem((int)MenuItemIds.DisplayResizable, Icon.None, "Resizable",
isChecked: isResizable, isEnabled: someOpsSelected, reserveIconColumn: false,
state: isResizable ? emphasized : muted))
{
foreach (var childUi in selectedChildUis)
{
childUi.Style = SymbolUi.Child.Styles.Resizable;
}
}
var isExpanded = selectedChildUis.Any(child => child.Style == SymbolUi.Child.Styles.Expanded);
if (CustomComponents.DrawMenuItem((int)MenuItemIds.DisplayExpanded, Icon.None, "Expanded",
isChecked: isExpanded, isEnabled: someOpsSelected, reserveIconColumn: false,
state: isExpanded ? emphasized : muted))
{
foreach (var childUi in selectedChildUis)
{
childUi.Style = SymbolUi.Child.Styles.Expanded;
}
}
CustomComponents.SeparatorLine();
var isImage = oneOpSelected
&& selectedChildUis[0].SymbolChild.Symbol.OutputDefinitions.Count > 0
&& selectedChildUis[0].SymbolChild.Symbol.OutputDefinitions[0].ValueType == typeof(Texture2D);
if (CustomComponents.DrawMenuItem((int)MenuItemIds.DisplayBackground, Icon.None, "Set image as graph background",
UserActions.DisplayImageAsBackground.ListShortcuts(), isChecked: false, isEnabled: isImage,
reserveIconColumn: false, state: muted))
{
var instance = context.CompositionInstance.Children[selectedChildUis[0].Id];
ProjectView.Focused.SetBackgroundOutput(instance);
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.DisplayPin, Icon.None, "Pin to output",
isChecked: false, isEnabled: oneOpSelected, reserveIconColumn: false, state: muted))
{
if (ProjectView.Focused != null)
NodeActions.PinSelectedToOutputWindow(ProjectView.Focused, nodeSelection, context.CompositionInstance);
}
ImGui.EndMenu();
}
private static void DrawEditMoreSubMenu(GraphUiContext context, NodeSelection nodeSelection, List<SymbolUi.Child> selectedChildUis,
bool oneOpSelected, bool someOpsSelected, bool isSaving)
{
const CustomComponents.ButtonStates muted = CustomComponents.ButtonStates.Default;
if (!CustomComponents.DrawSubMenu((int)MenuItemIds.MoreMenu, "more..."))
return;
// Copy variants. Keyframes and Dependencies are placeholders until the backing actions exist.
CustomComponents.DrawMenuItem((int)MenuItemIds.CopyKeyframes, Icon.None, "Copy Keyframes",
isChecked: false, isEnabled: false, reserveCheckmarkColumn: false, reserveIconColumn: false);
CustomComponents.TooltipForLastItem("Not implemented yet");
if (CustomComponents.DrawMenuItem((int)MenuItemIds.CopySymbolName, Icon.None, "Copy Symbol Name",
isChecked: false, isEnabled: oneOpSelected, reserveCheckmarkColumn: false, reserveIconColumn: false,
state: muted))
{
ImGui.SetClipboardText("[" + selectedChildUis[0].SymbolChild.ReadableName + "]");
}
CustomComponents.DrawMenuItem((int)MenuItemIds.CopyDependencies, Icon.None, "Copy Dependencies",
isChecked: false, isEnabled: false, reserveCheckmarkColumn: false, reserveIconColumn: false);
CustomComponents.TooltipForLastItem("Not implemented yet");
CustomComponents.SeparatorLine();
if (CustomComponents.DrawMenuItem((int)MenuItemIds.DuplicateConnected, Icon.None, "Duplicate Connected",
UserActions.DuplicateWithConnections.ListShortcuts(), isChecked: false,
isEnabled: someOpsSelected && !isSaving, reserveCheckmarkColumn: false, reserveIconColumn: false,
state: muted))
{
Modifications.DuplicateWithConnections(context);
context.Layout.FlagStructureAsChanged();
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.PasteValues, Icon.None, "Paste Values",
UserActions.PasteValues.ListShortcuts(), reserveCheckmarkColumn: false, reserveIconColumn: false,
state: muted))
{
NodeActions.PasteValues(nodeSelection, context.View, context.CompositionInstance);
context.Layout.FlagStructureAsChanged();
}
var symbolPackage = context.CompositionInstance.GetSymbolUi().Symbol.SymbolPackage;
if (!symbolPackage.IsReadOnly)
{
CustomComponents.SeparatorLine();
if (CustomComponents.DrawSubMenu((int)MenuItemIds.OpenFolderMenu, "Open folder...", reserveCheckmarkColumn: false))
{
if (CustomComponents.DrawMenuItem((int)MenuItemIds.OpenProjectFolder, Icon.None, "Project",
reserveCheckmarkColumn: false, reserveIconColumn: false))
{
CoreUi.Instance.OpenWithDefaultApplication(symbolPackage.Folder);
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.OpenResourcesFolder, Icon.None, "Resources",
reserveCheckmarkColumn: false, reserveIconColumn: false))
{
CoreUi.Instance.OpenWithDefaultApplication(symbolPackage.AssetsFolder);
}
ImGui.EndMenu();
}
}
ImGui.EndMenu();
}
private static void DrawSymbolDefinitionSubMenu(GraphUiContext context, ProjectView projectView, List<SymbolUi.Child> selectedChildUis,
bool oneOpSelected, bool someOpsSelected, bool isSaving)
{
const CustomComponents.ButtonStates muted = CustomComponents.ButtonStates.Default;
if (!CustomComponents.DrawSubMenu((int)MenuItemIds.SymbolDefMenu, "Symbol Definition...", isEnabled: !isSaving))
return;
var canRename = oneOpSelected && !selectedChildUis[0].SymbolChild.Symbol.SymbolPackage.IsReadOnly;
if (CustomComponents.DrawMenuItem((int)MenuItemIds.RenameSymbol, Icon.None, "Rename Symbol",
isChecked: false, isEnabled: canRename, reserveCheckmarkColumn: false, reserveIconColumn: false,
state: muted))
{
context.RenameSymbolDialog.ShowNextFrame();
context.SymbolNameForDialogEdits = selectedChildUis[0].SymbolChild.Symbol.Name;
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.DuplicateAsNewType, Icon.None, "Duplicate as new Type",
isChecked: false, isEnabled: oneOpSelected, reserveCheckmarkColumn: false, reserveIconColumn: false,
state: muted))
{
context.SymbolNameForDialogEdits = selectedChildUis[0].SymbolChild.Symbol.Name ?? string.Empty;
context.NameSpaceForDialogEdits = selectedChildUis[0].SymbolChild.Symbol.Namespace ?? string.Empty;
context.SymbolDescriptionForDialog = selectedChildUis[0].SymbolChild.Symbol.GetSymbolUi().Description;
context.DuplicateSymbolDialog.ShowNextFrame();
}
if (CustomComponents.DrawMenuItem((int)MenuItemIds.CombineIntoNewType, Icon.None, "Combine into new Type",
isChecked: false, isEnabled: someOpsSelected, reserveCheckmarkColumn: false, reserveIconColumn: false,
state: muted))
{
context.NameSpaceForDialogEdits = projectView.CompositionInstance.Symbol.Namespace ?? string.Empty;
context.SymbolDescriptionForDialog = "";
context.CombineToSymbolDialog.ShowNextFrame();
}
CustomComponents.SeparatorLine();
var canSetThumbnail = oneOpSelected && !selectedChildUis[0].SymbolChild.Symbol.SymbolPackage.IsReadOnly
&& RenderProcess.MainOutputTexture != null;
if (CustomComponents.DrawMenuItem((int)MenuItemIds.SetThumbnail, Icon.None, "Set Thumbnail",
isChecked: false, isEnabled: canSetThumbnail, reserveCheckmarkColumn: false, reserveIconColumn: false,
state: muted))
{
ThumbnailManager.SaveThumbnail(selectedChildUis[0].SymbolChild.Symbol.Id,
selectedChildUis[0].SymbolChild.Symbol.SymbolPackage,
RenderProcess.MainOutputTexture,
ThumbnailManager.Categories.PackageMeta);
}
ImGui.EndMenu();
}
private static void ToggleSnapshotControlForSelection(GraphUiContext context, SymbolUi compositionSymbolUi, List<SymbolUi.Child> selectedChildUis)
{
var disableBecauseAllEnabled = selectedChildUis.TrueForAll(c => c.EnabledForSnapshots);
var macro = new MacroCommand("Toggle snapshot enabled");
macro.AddAndExecCommand(new ChangeSnapshotEnabledCommand(compositionSymbolUi.Symbol.Id,
selectedChildUis,
newEnabled: !disableBecauseAllEnabled));
var allSnapshots = VariationHandling.ActivePoolForSnapshots?.AllVariations;
if (allSnapshots != null && allSnapshots.Count > 0)
{
if (disableBecauseAllEnabled)
{
VariationHandling.RemoveInstancesFromVariations(selectedChildUis.Select(ui => ui.Id), allSnapshots, collectInto: macro);
}
else
{
var selectedInstances = selectedChildUis
.Select(ui => context.CompositionInstance.Children[ui.Id])
.ToList();
foreach (var snapshot in allSnapshots)
{
VariationHandling.ActivePoolForSnapshots.UpdateVariationPropertiesForInstances(snapshot, selectedInstances, collectInto: macro);
}
}
}
UndoRedoStack.Add(macro);
}
// Resolves a selected op's file-path input to an absolute video path, for the "Generate proxy" action.
private static bool TryGetVideoProxySource(Instance instance, out string absolutePath)
{
absolutePath = string.Empty;
if (!SymbolAnalysis.TryGetFileInputFromInstance(instance, out var fileInput, out _))
return false;
var address = fileInput.TypedInputValue.Value;
if (string.IsNullOrEmpty(address) || !IsVideoFile(address))
return false;
return AssetRegistry.TryResolveAddress(address, instance as IResourceConsumer, out absolutePath, out _)
&& File.Exists(absolutePath);
}
private static bool IsVideoFile(string path)
=> Path.GetExtension(path).ToLowerInvariant() is ".mp4" or ".mov" or ".mkv" or ".avi" or ".webm" or ".m4v" or ".mpg" or ".mpeg";
}