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; /// /// A that displays the graph of an Operator. /// 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(InputSlot 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 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 ==================================================================== /// /// Scales the canvas and handles basic canvas interactions /// 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 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().ToList(); var selectedOutputUis = _nodeSelection.GetSelectedNodes().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 SelectableChildren => _projectView?.CompositionInstance != null ? NodeSelection.GetSelectableChildren(_projectView.CompositionInstance) : []; //private readonly List _selectableItems = new(); #endregion #region public API bool IGraphView.Destroyed { get; set; } public void OpenAndFocusInstance(IReadOnlyList 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 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 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; } }