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

1099 lines
43 KiB
C#

using ImGuiNET;
using T3.Core.Operator;
using T3.Core.Operator.Slots;
using T3.Core.SystemUi;
using T3.Editor.Gui.Dialogs;
using T3.Editor.Gui.Graph.Dialogs;
using T3.Editor.Gui.Interaction;
using T3.Editor.Gui.Interaction.Keyboard;
using T3.Editor.Gui.Interaction.Variations;
using T3.Editor.Gui.Legacy.Interaction;
using T3.Editor.Gui.Legacy.Interaction.Connections;
using T3.Editor.Gui.MagGraph.Interaction;
using T3.Editor.Gui.OutputUi;
using T3.Editor.Gui.Styling;
using T3.Editor.Gui.UiHelpers;
using T3.Editor.Gui.Windows.Layouts;
using T3.Editor.UiModel;
using T3.Editor.UiModel.Commands;
using T3.Editor.UiModel.Commands.Graph;
using T3.Editor.UiModel.Exporting;
using T3.Editor.UiModel.InputsAndTypes;
using T3.Editor.UiModel.Modification;
using T3.Editor.UiModel.ProjectHandling;
using T3.Editor.UiModel.Selection;
namespace T3.Editor.Gui.Legacy;
/// <summary>
/// A <see cref="IGraphView"/> that displays the graph of an Operator.
/// </summary>
internal sealed class GraphView : ScalableCanvas, IGraphView
{
public SymbolBrowser SymbolBrowser { get; set; }
private readonly NodeSelection _nodeSelection;
private readonly NavigationHistory _navigationHistory;
private readonly NodeNavigation _nodeNavigation;
private readonly NodeGraphLayouting _nodeGraphLayouting;
private Legacy.Graph _graph;
public ScalableCanvas Canvas => this;
private ProjectView _projectView;
public ProjectView ProjectView
{
set
{
_projectView = value;
_graph = new Legacy.Graph(_projectView, this, () => SymbolBrowser);
}
}
public void Close()
{
ConnectionMaker.RemoveGraphView(this);
_nodeNavigation.FocusInstanceRequested -= OpenAndFocusInstance;
}
public void CreatePlaceHolderConnectedToInput(SymbolUi.Child symbolChildUi, Symbol.InputDefinition inputInputDefinition)
{
if (_projectView.InstView == null)
{
Log.Error("Failed to access composition op?");
return;
}
ConnectionMaker.StartFromInputSlot(this, _projectView.InstView.Symbol, symbolChildUi, inputInputDefinition);
var freePosition = NodeGraphLayouting.FindPositionForNodeConnectedToInput(_projectView.InstView.SymbolUi, symbolChildUi);
ConnectionMaker.InitSymbolBrowserAtPosition(this, SymbolBrowser, freePosition);
}
void IGraphView.ExtractAsConnectedOperator<T>(InputSlot<T> inputSlot, SymbolUi.Child symbolChildUi, Symbol.Child.Input input)
{
if (_projectView?.InstView == null)
return;
var freePosition = NodeGraphLayouting.FindPositionForNodeConnectedToInput(_projectView.InstView.SymbolUi, symbolChildUi);
ParameterExtraction.ExtractAsConnectedOperator(inputSlot, symbolChildUi, input, freePosition);
}
public void StartDraggingFromInputSlot(SymbolUi.Child symbolChildUi, Symbol.InputDefinition inputInputDefinition)
{
if (_projectView.InstView == null)
{
Log.Error("Failed to access composition op?");
return;
}
ConnectionMaker.StartFromInputSlot(this, _projectView.InstView.Symbol, symbolChildUi, inputInputDefinition);
}
public static ProjectView CreateWithComponents(OpenedProject openedProject)
{
ProjectView.CreateIndependentComponents(openedProject, out var navigationHistory, out var nodeSelection, out var graphImageBackground);
var projectView = new ProjectView(openedProject, navigationHistory, nodeSelection, graphImageBackground);
var graphView = new GraphView(nodeSelection,
openedProject.Structure,
navigationHistory,
projectView.NodeNavigation,
getComposition: () => projectView.CompositionInstance)
{
ProjectView = projectView
};
projectView.GraphView = graphView;
graphView.SymbolBrowser = new SymbolBrowser(projectView, graphView);
ConnectionMaker.GraphGraphView(graphView);
return projectView;
}
private GraphView(NodeSelection nodeSelection, Structure structure, NavigationHistory navigationHistory, NodeNavigation nodeNavigation,
Func<Instance> getComposition)
{
_nodeNavigation = nodeNavigation;
_nodeSelection = nodeSelection;
_navigationHistory = navigationHistory;
nodeNavigation.FocusInstanceRequested += OpenAndFocusInstance;
_nodeGraphLayouting = new NodeGraphLayouting(nodeSelection, structure);
SelectableNodeMovement = new SelectableNodeMovement(this, getComposition, () => SelectableChildren, _nodeSelection);
}
// moved to Close()
// ~GraphCanvas()
// {
// _nodeNavigation.FocusInstanceRequested -= OpenAndFocusInstance;
// }
[Flags]
public enum GraphDrawingFlags
{
None = 0,
PreventInteractions = 1 << 1,
HideGrid = 1 << 2,
}
#region drawing UI ====================================================================
/// <summary>
/// Scales the canvas and handles basic canvas interactions
/// </summary>
public void BeginDraw(bool backgroundActive, bool bgHasInteractionFocus)
{
var flags = GraphDrawingFlags.None;
if (backgroundActive)
flags |= GraphDrawingFlags.HideGrid;
if (bgHasInteractionFocus)
flags |= GraphDrawingFlags.PreventInteractions;
var preventInteractions = flags.HasFlag(GraphDrawingFlags.PreventInteractions);
_preventInteractions = preventInteractions;
_drawingFlags = flags;
var editingFlags = T3Ui.EditingFlags.None;
if (SymbolBrowser.IsOpen)
editingFlags |= T3Ui.EditingFlags.PreventZoomWithMouseWheel;
if (preventInteractions)
editingFlags |= T3Ui.EditingFlags.PreventMouseInteractions;
UpdateCanvas(out _, editingFlags);
}
void IGraphView.DrawGraph(ImDrawListPtr drawList, float graphOpacity)
{
if (_projectView?.CompositionInstance == null)
return;
ConnectionSnapEndHelper.PrepareNewFrame();
DrawDropHandler(_projectView.CompositionInstance, _projectView.CompositionInstance.GetSymbolUi());
ImGui.SetCursorScreenPos(Vector2.One * 100);
if (!_preventInteractions)
{
var compositionOp = _projectView.CompositionInstance;
var compositionUi = compositionOp.GetSymbolUi();
//compositionUi.FlagAsModified();
if (UserActions.FocusSelection.Triggered())
FocusViewToSelection();
if (!T3Ui.IsCurrentlySaving && UserActions.Duplicate.Triggered())
{
NodeActions.CopySelectedNodesToClipboard(_nodeSelection, compositionOp);
NodeActions.PasteClipboard(_nodeSelection, this, compositionOp);
}
if (!T3Ui.IsCurrentlySaving && UserActions.DeleteSelection.Triggered())
{
NodeActions.DeleteSelectedElements(_nodeSelection, compositionUi);
}
if (UserActions.ToggleDisabled.Triggered())
{
NodeActions.ToggleDisabledForSelectedElements(_nodeSelection);
}
if (UserActions.ToggleBypassed.Triggered())
{
NodeActions.ToggleBypassedForSelectedElements(_nodeSelection);
}
if (UserActions.PinToOutputWindow.Triggered())
{
if (LayoutHandling.FocusMode)
{
var selectedImage = _nodeSelection.GetFirstSelectedInstance();
if (selectedImage != null && ProjectView.Focused != null)
{
ProjectView.Focused.SetBackgroundOutput(selectedImage);
}
}
else
{
NodeActions.PinSelectedToOutputWindow(_projectView, _nodeSelection, compositionOp);
}
}
if (UserActions.DisplayImageAsBackground.Triggered())
{
var selectedImage = _nodeSelection.GetFirstSelectedInstance();
if (selectedImage != null && ProjectView.Focused != null)
{
ProjectView.Focused.SetBackgroundOutput(selectedImage);
//GraphWindow.Focused.SetBackgroundInstanceForCurrentGraph(selectedImage);
}
}
if (UserActions.CopyToClipboard.Triggered())
{
NodeActions.CopySelectedNodesToClipboard(_nodeSelection, compositionOp);
}
if (!T3Ui.IsCurrentlySaving && UserActions.PasteFromClipboard.Triggered())
{
NodeActions.PasteClipboard(_nodeSelection, this, compositionOp);
}
if (UserActions.LayoutSelection.Triggered())
{
_nodeGraphLayouting.ArrangeOps(compositionOp);
}
if (!T3Ui.IsCurrentlySaving && UserActions.AddSection.Triggered())
{
var newSection = NodeActions.AddSection(_nodeSelection, this, compositionOp);
_graph.RenameSection(newSection);
}
{
IReadOnlyList<Guid> navigationPath = null;
// Navigation
if (UserActions.NavigateBackwards.Triggered())
{
navigationPath = _navigationHistory.NavigateBackwards();
}
if (UserActions.NavigateForward.Triggered())
{
navigationPath = _navigationHistory.NavigateForward();
}
if (navigationPath != null)
_projectView.TrySetCompositionOp(navigationPath);
}
if (UserActions.SelectToAbove.Triggered())
{
_nodeNavigation.SelectAbove();
}
if (UserActions.SelectToRight.Triggered())
{
_nodeNavigation.SelectRight();
}
if (UserActions.SelectToBelow.Triggered())
{
_nodeNavigation.SelectBelow();
}
if (UserActions.AddComment.Triggered())
{
EditCommentDialog.ShowNextFrame();
}
if (UserActions.SelectToLeft.Triggered())
{
_nodeNavigation.SelectLeft();
}
if (UserActions.DisplayImageAsBackground.Triggered())
{
var selectedImage = _nodeSelection.GetFirstSelectedInstance();
if (selectedImage != null)
{
_projectView.GraphImageBackground.OutputInstance = selectedImage;
}
}
}
if (ImGui.IsWindowFocused() && !_preventInteractions)
{
var io = ImGui.GetIO();
var editingSomething = ImGui.IsAnyItemActive();
if (!io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !editingSomething)
{
if (UserActions.CameraForward.Triggered())
{
_dampedScrollVelocity.Y -= InverseTransformDirection(Vector2.One * UserSettings.Config.KeyboardScrollAcceleration).Y;
}
if (UserActions.CameraBackward.Triggered())
{
_dampedScrollVelocity.Y += InverseTransformDirection(Vector2.One * UserSettings.Config.KeyboardScrollAcceleration).Y;
}
if (UserActions.CameraLeft.Triggered())
{
_dampedScrollVelocity.X -= InverseTransformDirection(Vector2.One * UserSettings.Config.KeyboardScrollAcceleration).X;
}
if (UserActions.CameraRight.Triggered())
{
_dampedScrollVelocity.X += InverseTransformDirection(Vector2.One * UserSettings.Config.KeyboardScrollAcceleration).X;
}
if (UserActions.CameraDown.Triggered())
{
var center = WindowPos + WindowSize / 2;
ApplyZoomDelta(center, 1.05f, out _);
}
if (UserActions.CameraUp.Triggered())
{
var center = WindowPos + WindowSize / 2;
ApplyZoomDelta(center, 1 / 1.05f, out _);
}
}
}
ScrollTarget += _dampedScrollVelocity;
_dampedScrollVelocity *= 0.90f;
drawList.PushClipRect(WindowPos, WindowPos + WindowSize);
if (!_drawingFlags.HasFlag(GraphDrawingFlags.HideGrid))
DrawGrid(drawList);
if (ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem))
{
ConnectionSplitHelper.PrepareNewFrame(_projectView);
}
SymbolBrowser.Draw();
graphOpacity *= _preventInteractions ? 0.3f : 1;
_graph.DrawGraph(drawList, _drawingFlags.HasFlag(GraphDrawingFlags.PreventInteractions), _projectView.CompositionInstance, graphOpacity);
RenamingOperator.Draw(_projectView);
var tempConnections = ConnectionMaker.GetTempConnectionsFor(this);
var doubleClicked = ImGui.IsMouseDoubleClicked(0);
var isSomething = (ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup) || ImGui.IsWindowFocused())
&& !_preventInteractions
&& tempConnections.Count == 0;
var isOnBackground = ImGui.IsWindowFocused() && !ImGui.IsAnyItemActive();
var shouldHandleFenceSelection = isSomething
|| isOnBackground && (ImGui.IsMouseDoubleClicked(0) || UserActions.CloseOperator.Triggered());
if (shouldHandleFenceSelection)
HandleFenceSelection(_projectView.CompositionInstance, _selectionFence);
if (isOnBackground && (doubleClicked || UserActions.CloseOperator.Triggered()))
_projectView.TrySetCompositionOpToParent();
if (tempConnections.Count > 0 && ImGui.IsMouseReleased(0))
{
var isAnyItemHovered = ImGui.IsAnyItemHovered();
var droppedOnBackground =
ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem | ImGuiHoveredFlags.AllowWhenBlockedByPopup) && !isAnyItemHovered;
if (droppedOnBackground)
{
ConnectionMaker.InitSymbolBrowserAtPosition(this, SymbolBrowser, InverseTransformPositionFloat(ImGui.GetIO().MousePos));
}
else
{
var connectionDroppedOnBackground =
tempConnections[0].GetStatus() != ConnectionMaker.TempConnection.Status.TargetIsDraftNode;
if (connectionDroppedOnBackground)
{
//Log.Warning("Skipping complete operation on background drop?");
// ConnectionMaker.CompleteOperation();
}
}
}
drawList.PopClipRect();
var compositionInstance = _projectView.CompositionInstance;
if (FrameStats.Current.OpenedPopUpName == string.Empty)
CustomComponents.DrawContextMenuForScrollCanvas(() => DrawContextMenuContent(compositionInstance), ref _contextMenuIsOpen);
var symbolId = compositionInstance.Symbol.Id;
compositionInstance = null; // for assembly unloading
_duplicateSymbolDialog.Draw(symbolId, _nodeSelection.GetSelectedChildUis().ToList(), ref _nameSpaceForDialogEdits,
ref _symbolNameForDialogEdits,
ref _symbolDescriptionForDialog);
_combineToSymbolDialog.Draw(symbolId, _projectView,
ref _nameSpaceForDialogEdits,
ref _symbolNameForDialogEdits,
ref _symbolDescriptionForDialog);
compositionInstance = _projectView.CompositionInstance;
_renameSymbolDialog.Draw(_nodeSelection.GetSelectedChildUis().ToList(), ref _symbolNameForDialogEdits);
EditCommentDialog.Draw(_nodeSelection);
if (compositionInstance != _projectView.RootInstance && !compositionInstance.Symbol.SymbolPackage.IsReadOnly)
{
var symbol = compositionInstance.Symbol;
_addInputDialog.Draw(symbol);
_addOutputDialog.Draw(symbol);
}
LibWarningDialog.Draw(_projectView);
EditNodeOutputDialog.Draw();
SelectableNodeMovement.CompleteFrame();
}
public bool HasActiveInteraction
{
get
{
var t = ConnectionMaker.GetTempConnectionsFor(this);
return t.Count > 0;
}
}
private void HandleFenceSelection(Instance compositionOp, SelectionFence selectionFence)
{
switch (selectionFence.UpdateAndDraw(out var selectMode))
{
case SelectionFence.States.PressedButNotMoved:
if (selectMode == SelectionFence.SelectModes.Replace)
_nodeSelection.Clear();
break;
case SelectionFence.States.Updated:
var bounds = selectionFence.BoundsUnclamped;
HandleSelectionFenceUpdate(bounds, compositionOp, selectMode);
break;
case SelectionFence.States.CompletedAsClick:
// A hack to prevent clearing selection when opening parameter popup
if (ImGui.IsPopupOpen("", ImGuiPopupFlags.AnyPopup))
break;
_nodeSelection.Clear();
_nodeSelection.SetSelectionToComposition(compositionOp);
break;
}
}
private void HandleSelectionFenceUpdate(ImRect bounds, Instance compositionOp, SelectionFence.SelectModes selectMode)
{
var boundsInCanvas = InverseTransformRect(bounds);
var nodesToSelect = NodeSelection.GetSelectableChildren(compositionOp)
.Where(child => child is Section
? boundsInCanvas.Contains(child.Rect)
: child != null && child.Rect.Overlaps(boundsInCanvas));
if (selectMode == SelectionFence.SelectModes.Replace)
{
_nodeSelection.Clear();
}
var isRemoval = selectMode == SelectionFence.SelectModes.Remove;
foreach (var node in nodesToSelect)
{
if (node is SymbolUi.Child symbolChildUi)
{
if (!compositionOp.Children.TryGetChildInstance(symbolChildUi.Id, out var instance))
{
Log.Error("Can't find instance");
}
if (isRemoval)
{
_nodeSelection.DeselectNode(symbolChildUi, instance);
}
else
{
_nodeSelection.AddSelection(symbolChildUi, instance);
}
}
else
{
if (isRemoval)
{
_nodeSelection.DeselectNode(node);
}
else
{
_nodeSelection.AddSelection(node);
}
}
}
}
// private Symbol GetSelectedSymbol()
// {
// var selectedChildUi = GetSelectedChildUis().FirstOrDefault();
// return selectedChildUi != null ? selectedChildUi.SymbolChild.Symbol : compositionOp.Symbol;
// }
private void DrawDropHandler(Instance compositionOp, SymbolUi compositionOpSymbolUi)
{
if (!DragAndDropHandling.IsDragging)
return;
ImGui.SetCursorPos(Vector2.Zero);
ImGui.InvisibleButton("## drop", ImGui.GetWindowSize());
var result = DragAndDropHandling.TryHandleDropOnItem(DragAndDropHandling.DragTypes.Symbol, out var payload);
if (result == DragAndDropHandling.DragInteractionResult.None)
return;
if (!Guid.TryParse(payload, out var guid))
{
Log.Warning("Invalid data format for drop? " + payload);
return;
}
if (result == DragAndDropHandling.DragInteractionResult.Hovering)
{
DrawDragPreview(guid);
return;
}
if (result != DragAndDropHandling.DragInteractionResult.Dropped)
return;
if (SymbolUiRegistry.TryGetSymbolUi(guid, out var newSymbolUi))
{
var newSymbol = newSymbolUi.Symbol;
var posOnCanvas = InverseTransformPositionFloat(ImGui.GetMousePos());
var newChildUi = GraphOperations.AddSymbolChild(newSymbol, compositionOpSymbolUi, posOnCanvas);
var instance = compositionOp.Children[newChildUi.Id];
_nodeSelection.SetSelection(newChildUi, instance);
compositionOp.GetSymbolUi().FlagAsModified();
}
else
{
Log.Warning($"Symbol {guid} not found in registry");
}
}
private static void DrawDragPreview(Guid symbolId)
{
if (!SymbolUiRegistry.TryGetSymbolUi(symbolId, out var symbolUi))
return;
var symbol = symbolUi.Symbol;
var color = symbol.OutputDefinitions.Count > 0
? TypeUiRegistry.GetPropertiesForType(symbol.OutputDefinitions[0]?.ValueType).Color
: UiColors.Gray;
var mousePos = ImGui.GetMousePos();
var label = symbol.Name;
var textSize = ImGui.CalcTextSize(label);
var padding = new Vector2(6, 4);
var pos = mousePos + new Vector2(12, 12);
var size = textSize + padding * 2;
var drawList = ImGui.GetForegroundDrawList();
drawList.AddRectFilled(pos, pos + size, ColorVariations.OperatorBackground.Apply(color));
drawList.AddRect(pos, pos + size, ColorVariations.OperatorOutline.Apply(color));
drawList.AddText(pos + padding, ColorVariations.OperatorLabel.Apply(color), label);
}
public void FocusViewToSelection()
{
if (_projectView?.CompositionInstance == null)
return;
FitAreaOnCanvas(NodeSelection.GetSelectionBounds(_nodeSelection, _projectView.CompositionInstance));
}
public void FlagStructureAsChanged()
{
// The legacy graph rebuilds its view from the ui-model every frame
}
private void DrawContextMenuContent(Instance compositionOp)
{
var clickPosition = ImGui.GetMousePosOnOpeningCurrentPopup();
var compositionSymbolUi = compositionOp.GetSymbolUi();
var selectedChildUis = _nodeSelection.GetSelectedChildUis().ToList();
var nextUndoTitle = UndoRedoStack.CanUndo ? $" ({UndoRedoStack.GetNextUndoTitle()})" : string.Empty;
if (ImGui.MenuItem("Undo" + nextUndoTitle,
shortcut: UserActions.Undo.ListShortcuts(),
selected: false,
enabled: UndoRedoStack.CanUndo))
{
UndoRedoStack.Undo();
}
ImGui.Separator();
// ------ for selection -----------------------
var oneOpSelected = selectedChildUis.Count == 1;
var someOpsSelected = selectedChildUis.Count > 0;
var label = oneOpSelected
? $"{selectedChildUis[0].SymbolChild.ReadableName}..."
: $"{selectedChildUis.Count} selected items...";
ImGui.PushFont(Fonts.FontSmall);
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.Gray.Rgba);
ImGui.TextUnformatted(label);
ImGui.PopStyleColor();
ImGui.PopFont();
var allSelectedDisabled = selectedChildUis.TrueForAll(selectedChildUi => selectedChildUi.SymbolChild.IsDisabled);
if (ImGui.MenuItem("Disable",
UserActions.ToggleDisabled.ListShortcuts(),
selected: allSelectedDisabled,
enabled: selectedChildUis.Count > 0))
{
NodeActions.ToggleDisabledForSelectedElements(_nodeSelection);
}
var allSelectedBypassed = selectedChildUis.TrueForAll(selectedChildUi => selectedChildUi.SymbolChild.IsBypassed);
if (ImGui.MenuItem("Bypassed",
UserActions.ToggleBypassed.ListShortcuts(),
selected: allSelectedBypassed,
enabled: selectedChildUis.Count > 0))
{
NodeActions.ToggleBypassedForSelectedElements(_nodeSelection);
}
if (ImGui.MenuItem("Rename", oneOpSelected))
{
RenamingOperator.OpenForChildUi(selectedChildUis[0]);
}
if (ImGui.MenuItem("Add Comment",
UserActions.AddComment.ListShortcuts(),
selected: false,
enabled: oneOpSelected))
{
EditCommentDialog.ShowNextFrame();
}
if (ImGui.MenuItem("Arrange sub graph",
UserActions.LayoutSelection.ListShortcuts(),
selected: false,
enabled: someOpsSelected))
{
_nodeGraphLayouting.ArrangeOps(compositionOp);
}
var canModify = !compositionSymbolUi.Symbol.SymbolPackage.IsReadOnly;
if (canModify)
{
// Disable if already enabled for all
var disableBecauseAllEnabled
= selectedChildUis
.TrueForAll(c2 => c2.EnabledForSnapshots);
var macro = new MacroCommand("Toggle snapshot enabled");
macro.AddAndExecCommand(new ChangeSnapshotEnabledCommand(compositionSymbolUi.Symbol.Id,
selectedChildUis,
newEnabled: !disableBecauseAllEnabled));
// Add to add snapshots
var allSnapshots = VariationHandling.ActivePoolForSnapshots?.AllVariations;
if (allSnapshots != null && allSnapshots.Count > 0)
{
if (disableBecauseAllEnabled)
{
VariationHandling.RemoveInstancesFromVariations(selectedChildUis.Select(ui => ui.Id), allSnapshots, collectInto: macro);
}
// Remove from snapshots
else
{
var selectedInstances = selectedChildUis
.Select(ui => compositionOp.Children[ui.Id])
.ToList();
foreach (var snapshot in allSnapshots)
{
VariationHandling.ActivePoolForSnapshots.UpdateVariationPropertiesForInstances(snapshot, selectedInstances, collectInto: macro);
}
}
}
UndoRedoStack.Add(macro);
}
if (ImGui.BeginMenu("Display as..."))
{
if (ImGui.MenuItem("Small", "",
selected: selectedChildUis.Any(child => child.Style == SymbolUi.Child.Styles.Default),
enabled: someOpsSelected))
{
foreach (var childUi in selectedChildUis)
{
childUi.Style = SymbolUi.Child.Styles.Default;
}
}
if (ImGui.MenuItem("Resizable", "",
selected: selectedChildUis.Any(child => child.Style == SymbolUi.Child.Styles.Resizable),
enabled: someOpsSelected))
{
foreach (var childUi in selectedChildUis)
{
childUi.Style = SymbolUi.Child.Styles.Resizable;
}
}
if (ImGui.MenuItem("Expanded", "",
selected: selectedChildUis.Any(child => child.Style == SymbolUi.Child.Styles.Resizable),
enabled: someOpsSelected))
{
foreach (var childUi in selectedChildUis)
{
childUi.Style = SymbolUi.Child.Styles.Expanded;
}
}
ImGui.Separator();
var isImage = oneOpSelected
&& selectedChildUis[0].SymbolChild.Symbol.OutputDefinitions.Count > 0
&& selectedChildUis[0].SymbolChild.Symbol.OutputDefinitions[0].ValueType == typeof(T3.Core.DataTypes.Texture2D);
if (ImGui.MenuItem("Set image as graph background",
UserActions.DisplayImageAsBackground.ListShortcuts(),
selected: false,
enabled: isImage))
{
var instance = compositionOp.Children[selectedChildUis[0].Id];
_projectView.GraphImageBackground.OutputInstance = instance;
}
if (ImGui.MenuItem("Pin to output", oneOpSelected))
{
NodeActions.PinSelectedToOutputWindow(_projectView, _nodeSelection, compositionOp);
}
ImGui.EndMenu();
}
ImGui.Separator();
if (ImGui.MenuItem("Copy",
UserActions.CopyToClipboard.ListShortcuts(),
selected: false,
enabled: someOpsSelected))
{
NodeActions.CopySelectedNodesToClipboard(_nodeSelection, compositionOp);
}
if (ImGui.MenuItem("Paste", UserActions.PasteFromClipboard.ListShortcuts()))
{
NodeActions.PasteClipboard(_nodeSelection, this, compositionOp);
}
var selectedInputUis = _nodeSelection.GetSelectedNodes<IInputUi>().ToList();
var selectedOutputUis = _nodeSelection.GetSelectedNodes<IOutputUi>().ToList();
var isSaving = T3Ui.IsCurrentlySaving;
if (ImGui.MenuItem("Delete",
shortcut: "Del", // dynamic assigned shortcut is too long
selected: false,
enabled: (someOpsSelected || selectedInputUis.Count > 0 || selectedOutputUis.Count > 0) && !isSaving))
{
NodeActions.DeleteSelectedElements(_nodeSelection, compositionSymbolUi, selectedChildUis, selectedInputUis, selectedOutputUis);
}
if (ImGui.MenuItem("Duplicate",
UserActions.Duplicate.ListShortcuts(),
selected: false,
enabled: selectedChildUis.Count > 0 && !isSaving))
{
NodeActions.CopySelectedNodesToClipboard(_nodeSelection, compositionOp);
NodeActions.PasteClipboard(_nodeSelection, this, compositionOp);
}
ImGui.Separator();
if (ImGui.MenuItem("Replace with...", someOpsSelected && !isSaving))
{
var startingSearchString = selectedChildUis[0].SymbolChild.Symbol.Name;
var position = selectedChildUis.Count == 1 ? selectedChildUis[0].PosOnCanvas : InverseTransformPositionFloat(ImGui.GetMousePos());
SymbolBrowser.OpenAt(position, null, null, false, startingSearchString,
symbol => { ChangeSymbol.ChangeOperatorSymbol(_nodeSelection, compositionOp, selectedChildUis, symbol); });
}
if (ImGui.BeginMenu("Symbol definition...", !isSaving))
{
if (ImGui.MenuItem("Rename Symbol", oneOpSelected))
{
_renameSymbolDialog.ShowNextFrame();
_symbolNameForDialogEdits = selectedChildUis[0].SymbolChild.Symbol.Name;
//NodeOperations.RenameSymbol(selectedChildUis[0].SymbolChild.Symbol, "NewName");
}
if (ImGui.MenuItem("Duplicate as new type...", oneOpSelected))
{
_symbolNameForDialogEdits = selectedChildUis[0].SymbolChild.Symbol.Name ?? string.Empty;
_nameSpaceForDialogEdits = selectedChildUis[0].SymbolChild.Symbol.Namespace ?? string.Empty;
_symbolDescriptionForDialog = "";
_duplicateSymbolDialog.ShowNextFrame();
}
if (ImGui.MenuItem("Combine into new type...", someOpsSelected))
{
_nameSpaceForDialogEdits = compositionOp.Symbol.Namespace ?? string.Empty;
_symbolDescriptionForDialog = "";
_combineToSymbolDialog.ShowNextFrame();
}
ImGui.EndMenu();
}
//}
var symbolPackage = compositionSymbolUi.Symbol.SymbolPackage;
if (!symbolPackage.IsReadOnly)
{
if (ImGui.BeginMenu("Open folder..."))
{
if (ImGui.MenuItem("Project"))
{
CoreUi.Instance.OpenWithDefaultApplication(symbolPackage.Folder);
}
if (ImGui.MenuItem("Resources"))
{
CoreUi.Instance.OpenWithDefaultApplication(symbolPackage.AssetsFolder);
}
ImGui.EndMenu();
}
}
if (ImGui.BeginMenu("Add..."))
{
if (ImGui.MenuItem("Add Node...", "TAB", false, true))
{
SymbolBrowser.OpenAt(InverseTransformPositionFloat(clickPosition), null, null, false);
}
if (canModify)
{
if (ImGui.MenuItem("Add input parameter..."))
{
_addInputDialog.ShowNextFrame();
}
if (ImGui.MenuItem("Add output..."))
{
_addOutputDialog.ShowNextFrame();
}
}
if (ImGui.MenuItem("Add Section",
shortcut: UserActions.AddSection.ListShortcuts(),
selected: false,
enabled: true))
{
var newSection = NodeActions.AddSection(_nodeSelection, this, compositionOp);
_graph.RenameSection(newSection);
}
ImGui.EndMenu();
}
ImGui.Separator();
if (ImGui.MenuItem("Export as Executable", oneOpSelected))
{
switch (PlayerExporter.TryExportInstance(compositionOp, selectedChildUis.Single(), out var reason, out var exportDir))
{
case false:
Log.Error(reason);
BlockingWindow.Instance.ShowMessageBox(reason, $"Failed to export {label}");
break;
default:
Log.Info(reason);
BlockingWindow.Instance.ShowMessageBox(reason, $"Exported {label} successfully!");
// open export directory in native file explorer
CoreUi.Instance.OpenWithDefaultApplication(exportDir);
break;
}
}
if (oneOpSelected)
{
var symbol = selectedChildUis.Single().SymbolChild.Symbol;
CustomComponents.DrawSymbolCodeContextMenuItem(symbol);
// var childUi = selectedChildUis.Single();
// get instance that is currently selected
//var instance = compositionOp.Children[childUi.Id];
// if (NodeActions.TryGetShaderPath(instance, out var filePath, out var owner))
// {
// var shaderIsReadOnly = owner.IsReadOnly;
//
// if (ImGui.MenuItem("Open in Shader Editor", true))
// {
// if (shaderIsReadOnly)
// {
// CopyToTempShaderPath(filePath, out filePath);
// BlockingWindow.Instance.ShowMessageBox("Warning - viewing a read-only shader. Modifications will not be saved.\n" +
// "Following #include directives outside of the temp folder may lead you to read-only files, " +
// "and editing those can break operators.\n\nWith great power...", "Warning");
// }
//
// EditorUi.Instance.OpenWithDefaultApplication(filePath);
// }
// }
}
}
// private static void CopyToTempShaderPath(string filePath, out string newFilePath)
// {
// var directory = Path.GetDirectoryName(filePath)!;
// var destinationDirectory = Path.Combine(FileLocations.TempFolder, "ReadOnlyShaders");
//
// if (Directory.Exists(destinationDirectory))
// {
// try
// {
// Directory.Delete(destinationDirectory, true);
// }
// catch (Exception e)
// {
// Log.Warning($"Failed to delete temp directory: {e}");
// }
// }
//
// var directoryInfo = Directory.CreateDirectory(destinationDirectory);
//
// // copy all files in directory to temp directory for intellisense to work
// var allFilesInDirectory = Directory.EnumerateFiles(directory);
// foreach (var file in allFilesInDirectory)
// {
// var destinationPath = Path.Combine(destinationDirectory, Path.GetFileName(file));
// File.Copy(file, destinationPath);
// }
//
// ShaderLinter.AddPackage(new ShaderCompiler.ShaderResourcePackage(directoryInfo), ResourceManager.SharedShaderPackages,
// replaceExisting: true);
// newFilePath = Path.Combine(destinationDirectory, Path.GetFileName(filePath));
// }
private void DrawGrid(ImDrawListPtr drawList)
{
var gridSize = Math.Abs(64.0f * Scale.X);
for (var x = (-Scroll.X * Scale.X) % gridSize; x < WindowSize.X; x += gridSize)
{
drawList.AddLine(new Vector2(x, 0.0f) + WindowPos,
new Vector2(x, WindowSize.Y) + WindowPos, UiColors.CanvasGrid);
}
for (var y = (-Scroll.Y * Scale.Y) % gridSize; y < WindowSize.Y; y += gridSize)
{
drawList.AddLine(
new Vector2(0.0f, y) + WindowPos,
new Vector2(WindowSize.X, y) + WindowPos, UiColors.CanvasGrid);
}
}
private IEnumerable<ISelectableCanvasObject> SelectableChildren => _projectView?.CompositionInstance != null
? NodeSelection.GetSelectableChildren(_projectView.CompositionInstance)
: [];
//private readonly List<ISelectableCanvasObject> _selectableItems = new();
#endregion
#region public API
bool IGraphView.Destroyed { get; set; }
public void OpenAndFocusInstance(IReadOnlyList<Guid> path)
{
if (path.Count == 1)
{
_projectView.TrySetCompositionOp(path, ScalableCanvas.Transition.JumpOut, path[0]);
return;
}
var compositionPath = path.Take(path.Count - 1).ToList();
_projectView.TrySetCompositionOp(compositionPath, ScalableCanvas.Transition.JumpIn, path[^1]);
}
public void OpenAndFocusSection(IReadOnlyList<Guid> compositionPath, Guid sectionId)
{
if (compositionPath.Count == 1)
{
if (!_projectView.TrySetCompositionOp(compositionPath, ScalableCanvas.Transition.JumpOut, compositionPath[0]))
{
return;
}
}
else
{
var compositionPath2 = compositionPath.Take(compositionPath.Count - 1).ToList();
var compositionId = compositionPath[^1];
if (!_projectView.TrySetCompositionOp(compositionPath2, ScalableCanvas.Transition.JumpIn, compositionId))
return;
}
// We assume that composition has been set....
if (_projectView.CompositionInstance == null)
return;
var symbolUi = _projectView.CompositionInstance.GetSymbolUi();
if (!symbolUi.Sections.TryGetValue(sectionId, out var section))
return;
var area = ImRect.RectWithSize(section.PosOnCanvas, section.Size);
RequestTargetViewAreaWithTransition(area, Transition.Smooth);
}
public void OpenAndFocusInputOrOutput(IReadOnlyList<Guid> compositionPath, Guid ioId)
{
if (compositionPath.Count == 1)
{
if (!_projectView.TrySetCompositionOp(compositionPath, ScalableCanvas.Transition.JumpOut, compositionPath[0]))
return;
}
else
{
if (!_projectView.TrySetCompositionOp(compositionPath, ScalableCanvas.Transition.JumpIn, compositionPath[^1]))
return;
}
if (_projectView.CompositionInstance == null)
return;
var symbolUi = _projectView.CompositionInstance.GetSymbolUi();
ISelectableCanvasObject ioUi = null;
if (symbolUi.InputUis.TryGetValue(ioId, out var inputUi))
{
ioUi = inputUi;
}
else if (symbolUi.OutputUis.TryGetValue(ioId, out var outputUi))
{
ioUi = outputUi;
}
if (ioUi == null)
return;
_projectView.NodeSelection.SetSelection(ioUi);
var area = ImRect.RectWithSize(ioUi.PosOnCanvas, ioUi.Size);
area.Expand(150);
RequestTargetViewAreaWithTransition(area, Transition.Smooth);
}
#endregion
private bool _contextMenuIsOpen;
private bool _preventInteractions;
private GraphDrawingFlags _drawingFlags;
private readonly SelectionFence _selectionFence = new();
private readonly AddInputDialog _addInputDialog = new();
private readonly AddOutputDialog _addOutputDialog = new();
private readonly CombineToSymbolDialog _combineToSymbolDialog = new();
private readonly DuplicateSymbolDialog _duplicateSymbolDialog = new();
private readonly RenameSymbolDialog _renameSymbolDialog = new();
public readonly EditNodeOutputDialog EditNodeOutputDialog = new();
public readonly EditCommentDialog EditCommentDialog = new();
public readonly LibWarningDialog LibWarningDialog = new();
private string _symbolNameForDialogEdits = "";
private string _symbolDescriptionForDialog = "";
private string _nameSpaceForDialogEdits = "";
private Vector2 _dampedScrollVelocity = Vector2.Zero;
protected override ScalableCanvas Parent => null;
public SelectableNodeMovement SelectableNodeMovement { get; }
}