#nullable enable using System.Diagnostics; using T3.Core.Operator; using T3.Editor.Gui.Dialogs; using T3.Editor.Gui.Graph.Dialogs; using T3.Editor.Gui.MagGraph.Interaction; using T3.Editor.Gui.MagGraph.Model; using T3.Editor.Gui.MagGraph.Ui; using T3.Editor.Gui.UiHelpers; using T3.Editor.UiModel.Commands; using T3.Editor.UiModel.Commands.Graph; using T3.Editor.UiModel.Modification; using T3.Editor.UiModel.ProjectHandling; using T3.Editor.UiModel.Selection; using MagItemMovement = T3.Editor.Gui.MagGraph.Interaction.MagItemMovement; namespace T3.Editor.Gui.MagGraph.States; /// /// Holds the current interaction state of the graph. It is passed as a parameter /// during most processing and makes "graph-global" components and states accessible to all related components. /// New instances of the context are created when the composition object or window changes. /// /// /// Overall concept of the graph UI system. /// /// Preface: The node graph is a central piece of Tooll's user interface and probably /// one of the most complex: For the implementation, we tried to balance the following: /// - Code should be well-structured and readable. /// - There should be no mud-ball side effects that lead to inconsistent states and hard-to-reproduce issues. /// - All interactions should support combined undo/redo out of the box. /// - The system should be open to implement new features, tweak the design, or adjust behaviors. /// - Ideally, rendering should be fast. /// - Cycle checks should be consistent and bulletproof. /// - Allocations should be avoided if possible. /// /// The basic components are: /// - holds an intermediate view model that is updated if required. This view model /// builds referenceable items, view elements, and connections. This makes it much easier to traverse the /// graph without dictionary lookups. The layout also precomputes the visibility of input links, which simplifies /// the layout and rendering of connection lines (one of the most complicated parts of the legacy layout). /// - holds the current interaction state of the graph. It is passed as a parameter /// during most processing and makes "graph-global" components and states accessible to all related components. /// New instances of the context are created when the composition object or window changes. /// - the state machine is a very bare-bones (no hierarchy or events) implementation /// of a state machine that handles activation of s. There can only be one state active. /// Most of the update interaction is done in State.Update() overrides. /// - is a scalable canvas that handles drawing. The Layout sometimes resets /// the current state. /// - combines drawing and logic that handles creating new operators (it's the /// "new" SymbolBrowser). /// - handles dragging, snapping, inserting, and unsnapping operators on the canvas. /// Grouping is handled on the fly by flood-filling "Snapped" Layout-Connections into HashSets. /// /// Notes: /// ------- /// Currently, a Context only holds information on a single composition and is discorded as soon as /// the user navigations to another composition-op (e.g. jumping up, down the composition stack. /// /// Architectural questions: /// ------------------------ /// We need to clarify how to deal with modal dialogs like "Edit Comment" /// /// /// internal sealed class GraphUiContext { internal GraphUiContext(ProjectView projectView, MagGraphView view) { ProjectView = projectView; View = view; ItemMovement = new MagItemMovement(this, view, Layout, projectView.NodeSelection); Placeholder = new PlaceholderCreation(); //EditCommentDialog = new EditCommentDialog(); StateMachine = new StateMachine(typeof(GraphStates), GraphStates.Default);// needs to be initialized last } internal readonly ProjectView ProjectView; internal Instance CompositionInstance => ProjectView.CompositionInstance!; internal NodeSelection Selector => ProjectView.NodeSelection; /** Used for fading out graph on left edge */ internal float GraphOpacity = 1; internal bool PreventInteraction => ProjectView.GraphImageBackground.HasInteractionFocus; internal readonly MagGraphView View; internal readonly MagItemMovement ItemMovement; internal readonly PlaceholderCreation Placeholder; internal readonly ConnectionHovering ConnectionHovering = new(); internal readonly MagGraphLayout Layout = new(); internal readonly StateMachine StateMachine; internal MacroCommand? MacroCommand { get; private set; } /** Keep for continuous update of dragged items */ internal ModifyCanvasElementsCommand? MoveElementsCommand; // Input picking... (should probably be moved into Placeholder) internal Type? DraggedPrimaryOutputType; internal MagGraphItem? ItemWithActiveCustomUi { get; set; } /** Set when clicking on a node. * Is cleared on the beginning of each frame when in default state. */ internal MagGraphItem? ActiveItem { get; set; } internal MagGraphItem? ActiveSourceItem; internal Guid ActiveSourceOutputId { get; set; } internal MagGraphItem.Directions ActiveOutputDirection { get; set; } internal MagGraphItem? ActiveTargetItem; internal Guid ActiveTargetInputId { get; set; } internal MagGraphItem.Directions ActiveInputDirection { get; set; } /** The item that is currently hovered by the mouse */ internal MagGraphItem? HoveredItem { get; set; } /** Only relevant while picking inputs or hovering an op while dragging connecting end. */ internal MagGraphItem? ItemForInputSelection; internal Guid ActiveSectionId { get; set; } /** Used to prevent disconnected inputLines from collapsing... */ internal readonly HashSet DisconnectedInputHashes = []; internal bool TryGetActiveOutputLine(out MagGraphItem.OutputLine outputLine) { if (ActiveSourceItem == null || ActiveSourceItem.OutputLines.Length == 0) { outputLine = default; return false; } foreach (var l in ActiveSourceItem .OutputLines) { if (l.Id != ActiveSourceOutputId) continue; outputLine = l; return true; } outputLine = default; return false; } internal bool TryGetActiveInputLine(out MagGraphItem.InputLine inputLine) { if (ActiveTargetItem == null || ActiveTargetItem.InputLines.Length == 0) { inputLine = default; return false; } foreach (var l in ActiveTargetItem .InputLines) { if (l.Id != ActiveTargetInputId) continue; inputLine = l; return true; } inputLine = default; return false; } internal Vector2 PeekAnchorInCanvas; internal bool ShouldAttemptToSnapToInput; internal MacroCommand StartMacroCommand(string title) { Debug.Assert(MacroCommand == null); MacroCommand = new MacroCommand(title); return MacroCommand; } internal MacroCommand StartOrContinueMacroCommand(string title) { MacroCommand ??= new MacroCommand(title); return MacroCommand; } internal void CompleteMacroCommand() { Debug.Assert(MacroCommand != null); UndoRedoStack.Add(MacroCommand); MacroCommand = null; } internal void CancelMacroCommand() { Debug.Assert(MacroCommand != null); MacroCommand.Undo(); MacroCommand = null; } // Dialogs internal readonly EditCommentDialog EditCommentDialog = new(); internal readonly AddInputDialog AddInputDialog = new(); internal readonly AddOutputDialog AddOutputDialog = new(); internal readonly RenameOutputDialog RenameOutputDialog = new(); internal readonly CombineToSymbolDialog CombineToSymbolDialog = new(); internal readonly DuplicateSymbolDialog DuplicateSymbolDialog = new(); public readonly RenameSymbolDialog RenameSymbolDialog = new(); // Variables for dialogs internal string SymbolNameForDialogEdits = ""; internal string NameSpaceForDialogEdits = ""; internal string SymbolDescriptionForDialog =""; internal readonly List TempConnections = []; public ChangeSymbol.SymbolModificationResults DrawDialogs(ProjectView projectView) { EditCommentDialog.Draw(Selector); var results = ChangeSymbol.SymbolModificationResults.Nothing; if (projectView.CompositionInstance != null) { var compositionSymbol = projectView.InstView!.Symbol; if (projectView.CompositionInstance != projectView.RootInstance && !compositionSymbol.SymbolPackage.IsReadOnly) { results |= AddInputDialog.Draw(compositionSymbol); results |= AddOutputDialog.Draw(compositionSymbol); RenameOutputDialog.Draw(); } results |= DuplicateSymbolDialog.Draw(compositionSymbol.Id, projectView.NodeSelection.GetSelectedChildUis(), ref NameSpaceForDialogEdits, ref SymbolNameForDialogEdits, ref SymbolDescriptionForDialog); results |= CombineToSymbolDialog.Draw(compositionSymbol.Id, projectView, ref NameSpaceForDialogEdits, ref SymbolNameForDialogEdits, ref SymbolDescriptionForDialog); results |= EditTourPointsPopup.Draw(compositionSymbol, projectView); RenameSymbolDialog.Draw(projectView.NodeSelection.GetSelectedChildUis(), ref SymbolNameForDialogEdits); if(results != ChangeSymbol.SymbolModificationResults.Nothing) Layout.FlagStructureAsChanged(); } return results; } }