446 lines
16 KiB
C#
446 lines
16 KiB
C#
#nullable enable
|
|
using ImGuiNET;
|
|
using T3.Core.Operator;
|
|
using T3.Editor.App;
|
|
using T3.Editor.Gui.Input;
|
|
using T3.Editor.Gui.Styling;
|
|
using T3.Editor.Gui.UiHelpers;
|
|
using T3.Editor.UiModel;
|
|
using T3.Editor.UiModel.InputsAndTypes;
|
|
using T3.Editor.UiModel.ProjectHandling;
|
|
using T3.SystemUi;
|
|
|
|
namespace T3.Editor.Gui.Dialog;
|
|
|
|
[HelpUiID("SearchWindow")]
|
|
internal sealed class SearchDialog : ModalDialog
|
|
{
|
|
public SearchDialog()
|
|
{
|
|
DialogSize = new Vector2(500, 300);
|
|
Padding = 4;
|
|
}
|
|
|
|
public void Draw()
|
|
{
|
|
if (BeginDialog("Search"))
|
|
{
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
|
|
|
if (!_isOpen)
|
|
{
|
|
_justOpened = true;
|
|
_isOpen = true;
|
|
}
|
|
else
|
|
{
|
|
_justOpened = false;
|
|
}
|
|
|
|
if (_justOpened)
|
|
{
|
|
ImGui.SetKeyboardFocusHere();
|
|
}
|
|
|
|
var needsUpdate = _justOpened;
|
|
FormInputs.SetIndentToLeft();
|
|
|
|
FormInputs.SetWidth(0.7f);
|
|
needsUpdate |= FormInputs.AddStringInput("##searchInput", ref _searchString, "Search", null, null);
|
|
ImGui.SameLine(0,10);
|
|
|
|
FormInputs.SetWidth(1f);
|
|
needsUpdate |= FormInputs.AddEnumDropdown(ref _searchMode, "");
|
|
FormInputs.ResetWidth();
|
|
FormInputs.AddVerticalSpace();
|
|
|
|
if (needsUpdate)
|
|
{
|
|
UpdateResults();
|
|
}
|
|
|
|
var clickedOutside = ImGui.IsMouseClicked(ImGuiMouseButton.Left)
|
|
&& !ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows | ImGuiHoveredFlags.AllowWhenBlockedByActiveItem);
|
|
if (ImGui.IsKeyReleased(ImGuiKey.Enter) || ImGui.IsKeyReleased(ImGuiKey.Escape) || clickedOutside)
|
|
{
|
|
ImGui.CloseCurrentPopup();
|
|
}
|
|
|
|
DrawResultsList();
|
|
ImGui.PopStyleVar();
|
|
EndDialogContent();
|
|
}
|
|
else
|
|
{
|
|
_isOpen = false;
|
|
}
|
|
|
|
EndDialog();
|
|
}
|
|
|
|
private bool _isOpen;
|
|
|
|
private void DrawResultsList()
|
|
{
|
|
var size = ImGui.GetContentRegionAvail();
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(5, 5));
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(10, 10));
|
|
|
|
var matchingItems = _matchingItems;
|
|
|
|
if (ImGui.BeginChild("##results", size, ImGuiChildFlags.FrameStyle))
|
|
{
|
|
if (ImGui.IsKeyReleased(Key.CursorDown.ToImGuiKey()))
|
|
{
|
|
UiListHelpers.AdvanceSelectedItem(matchingItems!, ref _selectedInstance!, 1);
|
|
_selectedItemChanged = true;
|
|
var index = matchingItems.IndexOf(_selectedInstance);
|
|
if (index == 0)
|
|
ImGui.SetScrollY(0);
|
|
}
|
|
else if (ImGui.IsKeyReleased(Key.CursorUp.ToImGuiKey()))
|
|
{
|
|
UiListHelpers.AdvanceSelectedItem(matchingItems!, ref _selectedInstance!, -1);
|
|
_selectedItemChanged = true;
|
|
|
|
var index = matchingItems.IndexOf(_selectedInstance);
|
|
|
|
if (index * ImGui.GetTextLineHeight() > ImGui.GetScrollY() + ImGui.GetContentRegionAvail().Y)
|
|
{
|
|
Log.Debug("Would scroll down");
|
|
ImGui.SetScrollY(ImGui.GetScrollY() + ImGui.GetContentRegionAvail().Y);
|
|
}
|
|
}
|
|
|
|
unsafe
|
|
{
|
|
var clipperData = new ImGuiListClipper();
|
|
var listClipperPtr = new ImGuiListClipperPtr(&clipperData);
|
|
|
|
listClipperPtr.Begin(matchingItems.Count, ImGui.GetTextLineHeight());
|
|
while (listClipperPtr.Step())
|
|
{
|
|
for (var i = listClipperPtr.DisplayStart; i < listClipperPtr.DisplayEnd; ++i)
|
|
{
|
|
if (i < 0 || i >= matchingItems.Count)
|
|
continue;
|
|
|
|
DrawItem(matchingItems[i]);
|
|
}
|
|
}
|
|
|
|
listClipperPtr.End();
|
|
}
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
|
|
ImGui.PopStyleVar(2);
|
|
}
|
|
|
|
private void DrawItem(SearchResult item)
|
|
{
|
|
var components = item.GraphCanvas;
|
|
if (components == null)
|
|
return;
|
|
|
|
var symbolHash = item.Id.GetHashCode();
|
|
ImGui.PushID(symbolHash);
|
|
{
|
|
var instance = item.Instance;
|
|
if (item.Kind == SearchResult.Kinds.Instance)
|
|
{
|
|
var symbolNamespace = instance.Symbol.Namespace;
|
|
var isRelevantNamespace = symbolNamespace.StartsWith("Lib.")
|
|
|| symbolNamespace.StartsWith("examples.lib.");
|
|
|
|
var color = instance.Symbol.OutputDefinitions.Count > 0
|
|
? TypeUiRegistry.GetPropertiesForType(instance.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.Text, ColorVariations.OperatorLabel.Apply(color).Rgba);
|
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, 4));
|
|
|
|
var isSelected = item == _selectedInstance;
|
|
ImGui.PushID(item.Id.GetHashCode());
|
|
var hasBeenClicked = ImGui.Selectable("##Selectable", isSelected);
|
|
ImGui.PopID();
|
|
_selectedItemChanged |= hasBeenClicked;
|
|
|
|
var path = instance.InstancePath;
|
|
var readablePath = string.Join(" / ", components.OpenedProject.Structure.GetReadableInstancePath(path, false));
|
|
|
|
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
components.GraphView.OpenAndFocusInstance(path);
|
|
_selectedInstance = item;
|
|
_selectedItemChanged = false;
|
|
}
|
|
else if (_selectedItemChanged && _selectedInstance == item)
|
|
{
|
|
UiListHelpers.ScrollToMakeItemVisible();
|
|
components.GraphView.OpenAndFocusInstance(path);
|
|
_selectedItemChanged = false;
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
|
|
ImGui.TextUnformatted(instance.Symbol.Name);
|
|
|
|
if (instance.SymbolChild.HasCustomName)
|
|
{
|
|
ImGui.SameLine(0, 10);
|
|
CustomComponents.StylizedText($"\"{instance.SymbolChild.Name}\"", Fonts.FontNormal, UiColors.Gray);
|
|
}
|
|
|
|
ImGui.SameLine(0, 10);
|
|
|
|
CustomComponents.StylizedText(readablePath, Fonts.FontNormal, UiColors.Gray.Fade(0.5f));
|
|
|
|
ImGui.PopStyleVar();
|
|
ImGui.PopStyleColor(3);
|
|
}
|
|
else if (item.Section != null)
|
|
{
|
|
var isSelected = item == _selectedInstance;
|
|
var hasBeenClicked = ImGui.Selectable($"##Selectable{symbolHash.ToString()}", isSelected);
|
|
|
|
_selectedItemChanged |= hasBeenClicked;
|
|
|
|
var activate = false;
|
|
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
_selectedInstance = item;
|
|
activate = true;
|
|
}
|
|
else if (_selectedItemChanged && _selectedInstance == item)
|
|
{
|
|
UiListHelpers.ScrollToMakeItemVisible();
|
|
_selectedItemChanged = false;
|
|
activate = true;
|
|
}
|
|
|
|
if (activate)
|
|
{
|
|
components.GraphView.OpenAndFocusSection(item.Instance.InstancePath, item.Section.Id);
|
|
_selectedInstance = item;
|
|
}
|
|
|
|
ImGui.SameLine(0,2);
|
|
ImGui.TextUnformatted(item.Name);
|
|
|
|
ImGui.SameLine(0,10);
|
|
CustomComponents.StylizedText("(Section)", Fonts.FontNormal, UiColors.TextMuted);
|
|
|
|
}
|
|
else
|
|
{
|
|
var isSelected = item == _selectedInstance;
|
|
var hasBeenClicked = ImGui.Selectable($"##Selectable{symbolHash.ToString()}", isSelected);
|
|
|
|
_selectedItemChanged |= hasBeenClicked;
|
|
|
|
var activate = false;
|
|
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
_selectedInstance = item;
|
|
activate = true;
|
|
}
|
|
else if (_selectedItemChanged && _selectedInstance == item)
|
|
{
|
|
UiListHelpers.ScrollToMakeItemVisible();
|
|
_selectedItemChanged = false;
|
|
activate = true;
|
|
}
|
|
|
|
if (activate)
|
|
{
|
|
components.GraphView.OpenAndFocusInputOrOutput(item.Instance.InstancePath, item.Id);
|
|
_selectedInstance = item;
|
|
}
|
|
|
|
ImGui.SameLine(0,2);
|
|
ImGui.TextUnformatted(item.Name);
|
|
|
|
ImGui.SameLine(0,10);
|
|
CustomComponents.StylizedText(item.Kind == SearchResult.Kinds.InputNode ? "(Input)" : "(Output)", Fonts.FontNormal, UiColors.TextMuted);
|
|
}
|
|
}
|
|
|
|
ImGui.PopID();
|
|
}
|
|
|
|
private void UpdateResults()
|
|
{
|
|
_matchingItems.Clear();
|
|
|
|
var components = ProjectView.Focused;
|
|
if (components == null)
|
|
return;
|
|
|
|
// Show navigation / selection history if not searching
|
|
if (string.IsNullOrEmpty(_searchString))
|
|
{
|
|
var previousInstances = components.NavigationHistory
|
|
.GetPreviouslySelectedInstances()
|
|
.Select(instance => new SearchResult(instance));
|
|
|
|
_matchingItems.AddRange(previousInstances);
|
|
|
|
if (_matchingItems.Count > 0)
|
|
_selectedInstance = _matchingItems[0];
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
var compositionOp = components.CompositionInstance;
|
|
var composition = _searchMode switch
|
|
{
|
|
SearchModes.Global => components.RootInstance,
|
|
SearchModes.Local => compositionOp,
|
|
SearchModes.LocalAndInChildren => compositionOp,
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
};
|
|
|
|
if (composition == null)
|
|
return;
|
|
|
|
if (!string.IsNullOrEmpty(_searchString))
|
|
{
|
|
FindAllChildren(composition);
|
|
}
|
|
|
|
|
|
|
|
_matchingItems.Sort((foundA, foundB) =>
|
|
{
|
|
// 1. Sections and input/output nodes before instances
|
|
var aIsInstance = foundA.Kind == SearchResult.Kinds.Instance;
|
|
var bIsInstance = foundB.Kind == SearchResult.Kinds.Instance;
|
|
if (aIsInstance != bIsInstance)
|
|
{
|
|
return aIsInstance.CompareTo(bIsInstance);
|
|
}
|
|
|
|
// 2. Then sort by Name alphabetically
|
|
return string.Compare(foundA.Name, foundB.Name, StringComparison.Ordinal);
|
|
});
|
|
}
|
|
|
|
private void FindAllChildren(Instance composition)
|
|
{
|
|
var symbolUi = composition.GetSymbolUi();
|
|
|
|
foreach (var section in symbolUi.Sections.Values)
|
|
{
|
|
if (section.Label.Contains(_searchString, StringComparison.InvariantCultureIgnoreCase) ||
|
|
section.Title.Contains(_searchString, StringComparison.InvariantCultureIgnoreCase))
|
|
_matchingItems.Add(new SearchResult(composition, section));
|
|
}
|
|
|
|
foreach (var inputUi in symbolUi.InputUis.Values)
|
|
{
|
|
if (inputUi.InputDefinition.Name.Contains(_searchString, StringComparison.InvariantCultureIgnoreCase))
|
|
_matchingItems.Add(new SearchResult(composition, inputUi.InputDefinition.Id, inputUi.InputDefinition.Name, isInput: true));
|
|
}
|
|
|
|
foreach (var outputUi in symbolUi.OutputUis.Values)
|
|
{
|
|
if (outputUi.OutputDefinition.Name.Contains(_searchString, StringComparison.InvariantCultureIgnoreCase))
|
|
_matchingItems.Add(new SearchResult(composition, outputUi.OutputDefinition.Id, outputUi.OutputDefinition.Name, isInput: false));
|
|
}
|
|
|
|
foreach (var child in composition.Children.Values)
|
|
{
|
|
var symbolChild = child.SymbolChild;
|
|
if (child.Symbol.Name.Contains(_searchString, StringComparison.InvariantCultureIgnoreCase)
|
|
|| (symbolChild.HasCustomName && symbolChild.Name.Contains(_searchString, StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
_matchingItems.Add(new SearchResult(child));
|
|
}
|
|
|
|
if (_searchMode == SearchModes.Local)
|
|
continue;
|
|
|
|
|
|
FindAllChildren(child);
|
|
}
|
|
}
|
|
|
|
private readonly List<SearchResult> _matchingItems = new();
|
|
private bool _justOpened;
|
|
private static string _searchString = string.Empty;
|
|
private SearchResult? _selectedInstance;
|
|
private bool _selectedItemChanged;
|
|
private SearchModes _searchMode = SearchModes.Local;
|
|
|
|
private sealed class SearchResult
|
|
{
|
|
public enum Kinds
|
|
{
|
|
Instance,
|
|
Section,
|
|
InputNode,
|
|
OutputNode,
|
|
}
|
|
|
|
public SearchResult(Instance instance)
|
|
{
|
|
Kind = Kinds.Instance;
|
|
Instance = instance;
|
|
GraphCanvas = ProjectView.Focused;
|
|
Id = instance.SymbolChildId;
|
|
}
|
|
|
|
public SearchResult(Instance compositionInstance, Section section)
|
|
{
|
|
Kind = Kinds.Section;
|
|
Instance = compositionInstance;
|
|
Section = section;
|
|
GraphCanvas = ProjectView.Focused;
|
|
Id = section.Id;
|
|
}
|
|
|
|
public SearchResult(Instance compositionInstance, Guid ioId, string ioName, bool isInput)
|
|
{
|
|
Kind = isInput ? Kinds.InputNode : Kinds.OutputNode;
|
|
Instance = compositionInstance;
|
|
GraphCanvas = ProjectView.Focused;
|
|
Id = ioId;
|
|
_ioName = ioName;
|
|
}
|
|
|
|
public readonly Kinds Kind;
|
|
public readonly Guid Id;
|
|
public readonly Instance Instance;
|
|
public readonly Section? Section;
|
|
public readonly ProjectView? GraphCanvas;
|
|
|
|
public string Name => Kind switch
|
|
{
|
|
Kinds.Section => Section?.Label ?? string.Empty,
|
|
Kinds.InputNode or Kinds.OutputNode => _ioName,
|
|
_ => Instance.Symbol.Name,
|
|
};
|
|
|
|
private readonly string _ioName = string.Empty;
|
|
}
|
|
|
|
private enum SearchModes
|
|
{
|
|
Local,
|
|
LocalAndInChildren,
|
|
Global,
|
|
}
|
|
} |