using ImGuiNET; using T3.Core.DataTypes.Vector; using T3.Core.Model; using T3.Core.Operator; using T3.Editor.App; using T3.Editor.Gui.Interaction; using T3.Editor.Gui.Interaction.Variations; using T3.Editor.Gui.Interaction.Variations.Model; using T3.Editor.Gui.Legacy.Interaction.Connections; using T3.Editor.Gui.Styling; using T3.Editor.Gui.UiHelpers; using T3.Editor.UiModel; using T3.Editor.UiModel.Commands; using T3.Editor.UiModel.Commands.Graph; using T3.Editor.UiModel.Helpers; using T3.Editor.UiModel.InputsAndTypes; using T3.Editor.UiModel.ProjectHandling; using T3.SystemUi; namespace T3.Editor.Gui.Legacy.Interaction; /// /// Represents a placeholder for a new on the . /// It can be connected to other nodes and provide search functionality. It's basically the /// T2's CreateOperatorWindow. /// internal sealed class SymbolBrowser { private readonly ProjectView _components; private readonly IGraphView _graphView; public SymbolBrowser(ProjectView components, IGraphView graphView) { _components = components; _graphView = graphView; } #region public API ------------------------------------------------------------------------ public void OpenAt(Vector2 positionOnCanvas, Type filterInputType, Type filterOutputType, bool onlyMultiInputs, string startingSearchString = "", System.Action overrideCreate = null) { // Scroll canvas to avoid symbol-browser close too edge var screenPos = _graphView.Canvas.TransformPosition(positionOnCanvas); var screenRect = ImRect.RectWithSize(screenPos, SymbolUi.Child.DefaultOpSize); screenRect.Expand(200 * _graphView.Canvas.Scale.X); var windowRect = ImRect.RectWithSize(ImGui.GetWindowPos(), ImGui.GetWindowSize()); var tooCloseToEdge = !windowRect.Contains(screenRect); var canvasPosition = _graphView.Canvas.InverseTransformPositionFloat(screenPos); if (tooCloseToEdge) { var canvasRect = ImRect.RectWithSize(canvasPosition, SymbolUi.Child.DefaultOpSize); canvasRect.Expand(400); _graphView.Canvas.FitAreaOnCanvas(canvasRect); } //_prepareCommand = prepareCommand; _overrideCreate = overrideCreate; IsOpen = true; PosOnCanvas = positionOnCanvas; _focusInputNextTime = true; _filter.FilterInputType = filterInputType; _filter.FilterOutputType = filterOutputType; _filter.SearchString = startingSearchString; _selectedSymbolUi = null; _filter.OnlyMultiInputs = onlyMultiInputs; _filter.UpdateIfNecessary(_components.NodeSelection, forceUpdate: true); DrawUtils.DisableImGuiKeyboardNavigation(); if (_selectedSymbolUi == null && _filter.MatchingSymbolUis.Count > 0) { _selectedSymbolUi = _filter.MatchingSymbolUis[0]; } } public void Draw() { var canvas = _graphView.Canvas; var nodeSelection = _components.NodeSelection; if (!IsOpen) { var hasFocus = ImGui.IsWindowFocused(ImGuiFocusedFlags.ChildWindows); var anythingActive = ImGui.IsAnyItemActive(); if (!hasFocus || anythingActive || !ImGui.IsKeyReleased(Key.Tab.ToImGuiKey())) return; if (nodeSelection.GetSelectedChildUis().Count() != 1) { ConnectionMaker.StartOperation(_graphView, "Add operator"); var screenPos = ImGui.GetIO().MousePos + new Vector2(-4, -20); var canvasPosition = canvas.InverseTransformPositionFloat(screenPos); OpenAt(canvasPosition, null, null, false); return; } var childUi = nodeSelection.GetSelectedChildUis().ToList()[0]; { var instance = nodeSelection.GetInstanceForChildUi(childUi); if (instance == null) { nodeSelection.Clear(); return; } var screenPos = canvas.TransformPosition(childUi.PosOnCanvas); var screenRect = ImRect.RectWithSize(screenPos, SymbolUi.Child.DefaultOpSize); screenRect.Expand(200 * canvas.Scale.X); var windowRect = ImRect.RectWithSize(ImGui.GetWindowPos(), ImGui.GetWindowSize()); var tooCloseToEdge = !windowRect.Contains(screenRect); var canvasPosition = canvas.InverseTransformPositionFloat(screenPos); if (tooCloseToEdge) { var canvasRect = ImRect.RectWithSize(canvasPosition, SymbolUi.Child.DefaultOpSize); canvasRect.Expand(400); canvas.FitAreaOnCanvas(canvasRect); } ConnectionMaker.OpenBrowserWithSingleSelection(_components, childUi, instance, this); } return; } FrameStats.Current.OpenedPopUpName = "SymbolBrowser"; _filter.UpdateIfNecessary(_components.NodeSelection); ImGui.PushID(_uiId); { var posInWindow = canvas.ChildPosFromCanvas(PosOnCanvas); _posInScreen = canvas.TransformPosition(PosOnCanvas); _drawList = ImGui.GetWindowDrawList(); ImGui.SetNextWindowFocus(); var browserPositionInWindow = posInWindow + BrowserPositionOffset; var browserSize = ResultListSize; ClampPanelToCanvas(_graphView, ref browserPositionInWindow, ref browserSize); ImGui.SetCursorPos(browserPositionInWindow); DrawResultsList(browserSize); if (_selectedSymbolUi != null) { if (_filter.PresetFilterString != string.Empty && (_filter.WasUpdated || _selectedItemChanged)) { _matchingPresets.Clear(); var presetPool = VariationHandling.GetOrLoadVariations(_selectedSymbolUi.Symbol.Id); if (presetPool != null) { _matchingPresets.AddRange(presetPool.AllVariations.Where(v => v.IsPreset && v.Title.Contains(_filter.PresetFilterString, StringComparison.InvariantCultureIgnoreCase))); } } if ( _filter.PresetFilterString != string.Empty) { UiListHelpers.AdvanceSelectedItem(_matchingPresets, ref _selectedPreset, 0); DrawPresetPanel(browserPositionInWindow, new Vector2(140, browserSize.Y)); } else { _selectedPreset = null; DrawDescriptionPanel(browserPositionInWindow, browserSize); } } DrawSearchInput(posInWindow, _posInScreen, _size * T3Ui.UiScaleFactor); } ImGui.PopID(); } #endregion private System.Action _overrideCreate = null; //private bool IsSearchingPresets => _filter.MatchingPresets.Count > 0; private void DrawSearchInput(Vector2 posInWindow, Vector2 posInScreen, Vector2 size) { if (_focusInputNextTime) { ImGui.SetKeyboardFocusHere(); _focusInputNextTime = false; _selectedItemChanged = true; } ImGui.SetCursorPos(posInWindow); var padding = new Vector2(7, 6); ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, padding); ImGui.SetNextItemWidth(size.X); ImGui.InputText("##symbolBrowserFilter", ref _filter.SearchString, 20); // Search input outline _drawList.AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), UiColors.Gray); if (ImGui.IsKeyPressed(Key.Return.ToImGuiKey())) { if (_selectedSymbolUi != null) { CreateInstance(_selectedSymbolUi.Symbol); } } if (_filter.WasUpdated) { _selectedSymbolUi = _filter.MatchingSymbolUis.Count > 0 ? _filter.MatchingSymbolUis[0] : null; _selectedItemChanged = true; } var clickedOutside = ImGui.IsMouseClicked(ImGuiMouseButton.Left) && ImGui.IsWindowHovered(); var shouldCancelConnectionMaker = clickedOutside || ImGui.IsMouseClicked(ImGuiMouseButton.Right) || ImGui.IsKeyDown(Key.Esc.ToImGuiKey()); if (shouldCancelConnectionMaker) { //ConnectionMaker.AbortOperation(); Cancel(); } ImGui.PopStyleVar(); if (!ImGui.IsItemActive()) return; if (ImGui.IsMouseDragging(ImGuiMouseButton.Left)) { PosOnCanvas += _graphView.Canvas.InverseTransformDirection(ImGui.GetIO().MouseDelta); } } private void Cancel() { ConnectionMaker.AbortOperation(_graphView); // if (_prepareCommand != null) // { // _prepareCommand.Undo(); // _prepareCommand = null; // } Close(); } private void Close() { DrawUtils.RestoreImGuiKeyboardNavigation(); IsOpen = false; OnFocusRequested?.Invoke(); } private void DrawResultsList(Vector2 size) { ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(5, 5)); ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(10, 10)); ImGui.PushStyleColor(ImGuiCol.FrameBg, UiColors.BackgroundPopup.Rgba); if (ImGui.BeginChild("##results", size, ImGuiChildFlags.FrameStyle)) { // if (_filter.PresetFilterString == null) // { if (ImGui.IsKeyReleased(Key.CursorDown.ToImGuiKey())) { UiListHelpers.AdvanceSelectedItem(_filter.MatchingSymbolUis, ref _selectedSymbolUi, 1); _selectedItemChanged = true; } else if (ImGui.IsKeyReleased(Key.CursorUp.ToImGuiKey())) { UiListHelpers.AdvanceSelectedItem(_filter.MatchingSymbolUis, ref _selectedSymbolUi, -1); _selectedItemChanged = true; } // } var gotAMatch = _filter.MatchingSymbolUis.Count > 0 && !_filter.MatchingSymbolUis.Contains(_selectedSymbolUi); if (gotAMatch) _selectedSymbolUi = _filter.MatchingSymbolUis[0]; if ((_selectedSymbolUi == null && EditorSymbolPackage.AllSymbolUis.Any())) _selectedSymbolUi = EditorSymbolPackage.AllSymbolUis.First(); PrintTypeFilter(); var compositionOp = _components.CompositionInstance; var projectNamespace = "user." + compositionOp.Symbol.SymbolPackage.AssemblyInformation.Name + "."; var compositionNameSpace = ""; var currentMainComposition = compositionOp; compositionNameSpace = currentMainComposition.Symbol.Namespace; foreach (var symbolUi in _filter.MatchingSymbolUis) { var symbolHash = symbolUi.Symbol.Id.GetHashCode(); ImGui.PushID(symbolHash); { var symbolNamespace = symbolUi.Symbol.Namespace; var isRelevantNamespace = symbolNamespace.StartsWith("Lib.") || symbolNamespace.StartsWith("Types.") || symbolNamespace.StartsWith("Examples.Lib.") || symbolNamespace.StartsWith(projectNamespace) || symbolNamespace.StartsWith(compositionNameSpace); var color = symbolUi.Symbol.OutputDefinitions.Count > 0 ? TypeUiRegistry.GetPropertiesForType(symbolUi.Symbol.OutputDefinitions[0]?.ValueType).Color : UiColors.Gray; if (!isRelevantNamespace) { color = color.Fade(0.4f); } ImGui.PushStyleColor(ImGuiCol.Header, ColorVariations.OperatorBackground.Apply(color).Rgba); var hoverColor = ColorVariations.OperatorBackgroundHover.Apply(color).Rgba; hoverColor.W = 0.1f; ImGui.PushStyleColor(ImGuiCol.HeaderHovered, hoverColor); //ImGui.PushStyleColor(ImGuiCol.HeaderActive, ColorVariations.OperatorInputZone.Apply(color).Rgba); ImGui.PushStyleColor(ImGuiCol.Text, ColorVariations.OperatorLabel.Apply(color).Rgba); var isSelected = symbolUi == _selectedSymbolUi; _selectedItemChanged |= ImGui.Selectable($"##Selectable{symbolHash.ToString()}", isSelected); //bool selectionChangedToThis = isSelected && _selectedItemChanged; var isHovered = ImGui.IsItemHovered(); var hasMouseMoved = ImGui.GetIO().MouseDelta.LengthSquared() > 0; if (hasMouseMoved && isHovered) { _selectedSymbolUi = symbolUi; //_timeDescriptionSymbolUiLastHovered = DateTime.Now; _selectedItemChanged = true; } else if (_selectedItemChanged && _selectedSymbolUi == symbolUi) { UiListHelpers.ScrollToMakeItemVisible(); _selectedItemChanged = false; } if (ImGui.IsItemActivated()) { CreateInstance(symbolUi.Symbol); } ImGui.SameLine(); ImGui.TextUnformatted(symbolUi.Symbol.Name); ImGui.SameLine(); if (!string.IsNullOrEmpty(symbolNamespace)) { ImGui.PushStyleColor(ImGuiCol.Text, UiColors.Text.Fade(0.5f).Rgba); ImGui.Text(symbolNamespace); ImGui.PopStyleColor(); ImGui.SameLine(); } ImGui.NewLine(); ImGui.PopStyleColor(3); } ImGui.PopID(); } } ImGui.EndChild(); ImGui.PopStyleColor(); ImGui.PopStyleVar(2); } private void PrintTypeFilter() { if (_filter.FilterInputType == null && _filter.FilterOutputType == null) return; ImGui.PushFont(Fonts.FontSmall); var inputTypeName = _filter.FilterInputType != null ? TypeNameRegistry.Entries[_filter.FilterInputType] : string.Empty; var outputTypeName = _filter.FilterOutputType != null ? TypeNameRegistry.Entries[_filter.FilterOutputType] : string.Empty; var isMultiInput = _filter.OnlyMultiInputs ? "[..]" : ""; var headerLabel = $"{inputTypeName}{isMultiInput} -> {outputTypeName}"; ImGui.TextDisabled(headerLabel); ImGui.PopFont(); } private static void ClampPanelToCanvas(IGraphView graphView, ref Vector2 position, ref Vector2 size) { var maxXPos = position.X + size.X; var maxYPos = position.Y + size.Y; var windowSize = graphView.Canvas.WindowSize; var shouldShiftLeft = maxXPos > windowSize.X; var xPositionOffset = shouldShiftLeft ? windowSize.X - maxXPos : 0; var yOverflow = maxYPos > windowSize.Y; var yShrinkage = yOverflow ? windowSize.Y - maxYPos : 0; position.X += xPositionOffset; size.Y += yShrinkage; } private Variation _selectedPreset; private void DrawPresetPanel(Vector2 position, Vector2 size) { //size.X = 120; if (!TryFindValidPanelPosition(_graphView, ref position, size)) return; ImGui.PushStyleColor(ImGuiCol.FrameBg, UiColors.BackgroundPopup.Rgba); ImGui.SetCursorPos(position); if (ImGui.BeginChild("##presets", size, ImGuiChildFlags.FrameStyle)) { if (ImGui.IsKeyReleased(Key.CursorDown.ToImGuiKey())) { UiListHelpers.AdvanceSelectedItem(_matchingPresets, ref _selectedPreset, 1); } else if (ImGui.IsKeyReleased(Key.CursorUp.ToImGuiKey())) { UiListHelpers.AdvanceSelectedItem(_matchingPresets, ref _selectedPreset, -1); } ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.One); // Padding between panels ImGui.PushFont(Fonts.FontLarge); ImGui.TextUnformatted(_selectedSymbolUi.Symbol.Name); ImGui.PopFont(); ImGui.TextUnformatted("Presets"); ImGui.Dummy(Vector2.One * 10); if (_matchingPresets.Count == 0) { CustomComponents.EmptyWindowMessage("No presets found"); } else { foreach (var p in _matchingPresets) { if (ImGui.Selectable(p.Title, _selectedPreset == p)) { _selectedPreset = p; CreateInstance(_selectedSymbolUi.Symbol); } } } ImGui.PopStyleVar(); ImGui.EndChild(); } ImGui.PopStyleColor(); } private void DrawDescriptionPanel(Vector2 position, Vector2 size) { if (_selectedSymbolUi == null) return; var hasExamples = ExampleSymbolLinking.ExampleIdsForSymbolsId.TryGetValue(_selectedSymbolUi.Symbol.Id, out var examplesIds) && examplesIds.Count > 0; var hasDescription = !string.IsNullOrEmpty(_selectedSymbolUi.Description); if (!hasExamples && !hasDescription) return; if (!TryFindValidPanelPosition(_graphView, ref position, size)) return; ImGui.SetCursorPos(position); ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.One); // Padding between panels ImGui.PushStyleColor(ImGuiCol.FrameBg, UiColors.BackgroundPopup.Rgba); if (ImGui.BeginChild("##description", size, ImGuiChildFlags.FrameStyle)) { if (!string.IsNullOrEmpty(_selectedSymbolUi.Description)) { ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba); ImGui.TextWrapped(_selectedSymbolUi.Description); ImGui.PopStyleColor(); } if (hasExamples) { ImGui.Dummy(new Vector2(10, 10)); ListExampleOperators(examplesIds); } ImGui.EndChild(); } ImGui.PopStyleColor(); ImGui.PopStyleVar(); } private static bool TryFindValidPanelPosition(IGraphView graphView, ref Vector2 position, Vector2 size) { var maxXPos = position.X + size.X + ResultListSize.X; var wouldExceedWindowBounds = maxXPos >= graphView.Canvas.WindowSize.X; if (wouldExceedWindowBounds) { position.X -= size.X; //if it simply doesn't fit on screen, return if (position.X <= 0) return false; } else { position.X += size.X; } return true; } private static void ListExampleOperators(IEnumerable exampleIds) { ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f * ImGui.GetStyle().Alpha); foreach (var guid in exampleIds) { if (!SymbolUiRegistry.TryGetSymbolUi(guid, out var symbolUi)) return; const string label = "Example"; UiElements.DrawExampleOperator(symbolUi, label); } ImGui.PopStyleVar(); } private void CreateInstance(Symbol symbol) { if(_overrideCreate != null) { Close(); _overrideCreate(symbol); return; } var commandsForUndo = new List(); var parentOp = _components.CompositionInstance; var parentSymbol = parentOp.Symbol; var parentSymbolUi = parentSymbol.GetSymbolUi(); var addSymbolChildCommand = new AddSymbolChildCommand(parentSymbol, symbol.Id) { PosOnCanvas = PosOnCanvas }; commandsForUndo.Add(addSymbolChildCommand); addSymbolChildCommand.Do(); // Select new node if (!parentSymbolUi.ChildUis.TryGetValue(addSymbolChildCommand.AddedChildId, out var newChildUi)) { Log.Warning($"Unable to create new operator - failed to retrieve new child ui \"{addSymbolChildCommand.AddedChildId}\" " + $"from parent symbol ui {parentSymbolUi.Symbol}"); return; } var newSymbolChild = newChildUi.SymbolChild; var newInstance = _components.CompositionInstance.Children[newChildUi.Id]; var presetPool = VariationHandling.GetOrLoadVariations(_selectedSymbolUi.Symbol.Id); if (presetPool != null && _selectedPreset != null) { presetPool.Apply(newInstance, _selectedPreset); } _components.NodeSelection.SetSelection(newChildUi, newInstance); // if (_prepareCommand != null) // { // additionalCommands.Add(_prepareCommand); // } var tempConnections = ConnectionMaker.GetTempConnectionsFor(_graphView); foreach (var c in tempConnections) { switch (c.GetStatus()) { case ConnectionMaker.TempConnection.Status.SourceIsDraftNode: var outputDef = newSymbolChild.Symbol.GetOutputMatchingType(c.ConnectionType); if (outputDef == null) { Log.Error("Failed to find matching output connection type " + c.ConnectionType); return; } var newConnectionToSource = new Symbol.Connection(sourceParentOrChildId: newSymbolChild.Id, sourceSlotId: outputDef.Id, targetParentOrChildId: c.TargetParentOrChildId, targetSlotId: c.TargetSlotId); var addConnectionCommand = new AddConnectionCommand(parentSymbolUi.Symbol, newConnectionToSource, c.MultiInputIndex); addConnectionCommand.Do(); commandsForUndo.Add(addConnectionCommand); break; case ConnectionMaker.TempConnection.Status.TargetIsDraftNode: var inputDef = newSymbolChild.Symbol.GetInputMatchingType(c.ConnectionType); if (inputDef == null) { Log.Warning("Failed to complete node creation"); return; } var newConnectionToInput = new Symbol.Connection(sourceParentOrChildId: c.SourceParentOrChildId, sourceSlotId: c.SourceSlotId, targetParentOrChildId: newSymbolChild.Id, targetSlotId: inputDef.Id); var connectionCommand = new AddConnectionCommand(parentSymbolUi.Symbol, newConnectionToInput, c.MultiInputIndex); connectionCommand.Do(); commandsForUndo.Add(connectionCommand); break; } } // var newCommand = new MacroCommand("Insert Op", commands); // UndoRedoStack.Add(newCommand); ConnectionMaker.CompleteOperation(_graphView, commandsForUndo, "Insert Op " + newChildUi.SymbolChild.ReadableName); ParameterPopUp.NodeIdRequestedForParameterWindowActivation = newSymbolChild.Id; Close(); } private readonly SymbolFilter _filter = new(); public Vector2 PosOnCanvas { get; private set; } public Vector2 OutputPositionOnScreen => _posInScreen + _size; public bool IsOpen; private readonly Vector2 _size = SymbolUi.Child.DefaultOpSize; private static Vector2 BrowserPositionOffset => new(0, 40); private bool _focusInputNextTime; private Vector2 _posInScreen; private ImDrawListPtr _drawList; private bool _selectedItemChanged; private static Vector2 ResultListSize => new Vector2(250, 300) * new Vector2(2,1) * T3Ui.UiScaleFactor; private readonly Vector4 _namespaceColor = new Color(1, 1, 1, 0.4f); private SymbolUi _selectedSymbolUi; private static readonly int _uiId = "DraftNode".GetHashCode(); private readonly List _matchingPresets = new(); public event Action OnFocusRequested; }