项目文件夹

文件
2026-07-13 13:13:17 +08:00

518 行
18 KiB
C#

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#nullable enable
using System.Diagnostics;
using ImGuiNET;
using T3.Core.DataTypes.Vector;
using T3.Core.Model;
using T3.Core.Utils;
using T3.Editor.App;
using T3.Editor.Gui.Help;
using T3.Editor.Gui.MagGraph.Model;
using T3.Editor.Gui.MagGraph.States;
using T3.Editor.Gui.Styling;
using T3.Editor.Gui.UiHelpers;
using T3.Editor.Gui.Windows;
using T3.Editor.UiModel;
using T3.Editor.UiModel.Helpers;
using T3.Editor.UiModel.InputsAndTypes;
using T3.SystemUi;
namespace T3.Editor.Gui.MagGraph.Interaction;
internal static class PlaceHolderUi
{
internal static void Open(
GraphUiContext context,
MagGraphItem placeholderItem,
MagGraphItem.Directions connectionOrientation = MagGraphItem.Directions.Horizontal,
Type? inputFilter = null,
Type? outputFilter = null,
bool onlyMultiInputs = false)
{
_selectedSymbolUi = null;
_focusInputNextTime = true;
Filter.FilterInputType = inputFilter;
Filter.FilterOutputType = outputFilter;
Filter.OnlyMultiInputs = onlyMultiInputs;
Filter.WasUpdated = true;
Filter.SearchString = string.Empty;
Filter.UpdateIfNecessary(context.Selector, forceUpdate: true);
_placeholderItem = placeholderItem;
_connectionOrientation = connectionOrientation;
WindowContentExtend.GetLastAndReset();
SymbolBrowsing.Reset();
_rowHeight = 0;
}
internal static void Reset()
{
Filter.Reset();
_placeholderItem = null;
_selectedSymbolUi = null;
_rowHeight = 0;
_focusInputNextTime = false;
}
internal static UiResults Draw(GraphUiContext context, out SymbolUi? selectedUi)
{
selectedUi = null;
var drawList = ImGui.GetWindowDrawList();
var uiResult = UiResults.None;
if (_placeholderItem == null)
return uiResult;
FrameStats.Current.OpenedPopUpName = "SymbolBrowser";
Filter.UpdateIfNecessary(context.Selector);
uiResult |= DrawSearchInput(context, drawList);
var pMin = context.View.TransformPosition(_placeholderItem.PosOnCanvas);
var pMax = context.View.TransformPosition(_placeholderItem.Area.Max);
uiResult |= DrawResultsList(context, new ImRect(pMin, pMax), Filter, _connectionOrientation);
// Click outside = mouse click that is NOT in placeholder rect and NOT in results rect.
var clickedOutsidePlaceholder = ImGui.IsMouseClicked(ImGuiMouseButton.Left)
&& !_placeholderAreaOnScreen.Contains(ImGui.GetMousePos());
if (clickedOutsidePlaceholder && uiResult.HasFlag(UiResults.ClickedOutside))
{
uiResult |= UiResults.Cancel;
}
if (_focusInputNextTime)
{
ImGui.SetKeyboardFocusHere();
_focusInputNextTime = false;
}
selectedUi = _selectedSymbolUi;
return uiResult;
}
// TODO: Implement preset search
// if (_selectedSymbolUi != null)
// {
// if (Filter.PresetFilterString != string.Empty && (Filter.WasUpdated || _selectedItemChanged))
// {
//
// }
// }
private static UiResults DrawSearchInput(GraphUiContext context, ImDrawListPtr drawList)
{
var uiResult = UiResults.None;
Debug.Assert(_placeholderItem != null);
var canvasScale = context.View.Scale.X;
var item = _placeholderItem;
var pMin = context.View.TransformPosition(item.PosOnCanvas);
var pMax = context.View.TransformPosition(item.PosOnCanvas + item.Size);
var pMinVisible = pMin;
var pMaxVisible = pMax;
_placeholderAreaOnScreen = ImRect.RectBetweenPoints(pMin, pMax);
// Background and Outline
drawList.AddRectFilled(pMinVisible + Vector2.One * canvasScale,
pMaxVisible - Vector2.One,
UiColors.BackgroundFull,
2 * canvasScale);
if (_focusInputNextTime)
{
ImGui.SetKeyboardFocusHere();
_focusInputNextTime = false;
uiResult |= UiResults.SelectionChanged;
}
var labelPos = new Vector2(pMin.X,
(pMin.Y + pMax.Y) / 2 - ImGui.GetFrameHeight() / 2);
var posInWindow = labelPos - ImGui.GetWindowPos();
ImGui.SetCursorPos(posInWindow);
var favoriteGroup = SymbolBrowsing.IsFilterActive ? SymbolBrowsing.FilterString : string.Empty;
if (string.IsNullOrEmpty(favoriteGroup))
{
var padding = new Vector2(9, 3);
if (string.IsNullOrEmpty(Filter.SearchString))
{
drawList.AddText(labelPos + padding, UiColors.TextDisabled, "search...");
}
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, padding);
ImGui.PushStyleColor(ImGuiCol.FrameBg, Color.Transparent.Rgba);
ImGui.SetNextItemWidth(item.Size.X);
ImGui.PushID("SymbolBrowserSearch");
ImGui.InputText("##symbolBrowserFilter",
ref Filter.SearchString,
20,
ImGuiInputTextFlags.AutoSelectAll);
ImGui.PopID();
ImGui.PopStyleColor();
ImGui.PopStyleVar();
}
else
{
ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(5, 0));
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 10);
ImGui.PushStyleColor(ImGuiCol.Button, UiColors.ForegroundFull.Fade(0.1f).Rgba);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UiColors.ForegroundFull.Fade(0.2f).Rgba);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UiColors.ForegroundFull.Fade(0.3f).Rgba);
if (ImGui.Button(favoriteGroup + " ×"))
{
SymbolBrowsing.Reset();
}
ImGui.PopStyleColor(3);
ImGui.PopStyleVar();
}
if (ImGui.IsKeyPressed(Key.Return.ToImGuiKey()))
{
if (_selectedSymbolUi != null)
{
uiResult |= UiResults.Create;
}
}
if (Filter.WasUpdated)
{
_selectedSymbolUi = Filter.MatchingSymbolUis.Count > 0
? Filter.MatchingSymbolUis[0]
: null;
_rowHeight = 0;
uiResult |= UiResults.SelectionChanged;
}
// Preserve Esc-based cancel logic with shouldCancelConnectionMaker.
var clickedOutside = false; // TODO: wire this up if window-hover based cancel is reintroduced
var shouldCancelConnectionMaker = clickedOutside
// ImGui.IsMouseClicked(ImGuiMouseButton.Right)
|| ImGui.IsKeyDown(Key.Esc.ToImGuiKey());
if (shouldCancelConnectionMaker)
{
uiResult |= UiResults.Cancel;
//Cancel(context); // TODO: Implement cancel behavior if/when needed
}
if (!ImGui.IsItemActive())
return uiResult;
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left))
{
_placeholderItem.PosOnCanvas += context.View.InverseTransformDirection(ImGui.GetIO().MouseDelta);
}
return uiResult;
}
private static UiResults DrawResultsList(
GraphUiContext context,
ImRect screenItemArea,
SymbolFilter filter,
MagGraphItem.Directions orientation)
{
var result = UiResults.None;
var popUpSize = new Vector2(150, 235) * T3Ui.UiScaleFactor;
var windowSize = ImGui.GetWindowSize();
var windowPos = ImGui.GetWindowPos();
Vector2 resultPosOnScreen = new Vector2(screenItemArea.Min.X, screenItemArea.Max.Y + 3);
if (orientation == MagGraphItem.Directions.Vertical)
{
var y = screenItemArea.GetCenter().Y - 0.1f * popUpSize.Y;
resultPosOnScreen.Y = y.Clamp(windowPos.Y + 10, windowSize.Y + windowPos.Y - popUpSize.Y - 10);
resultPosOnScreen.X = screenItemArea.Max.X.Clamp(windowPos.X + 10,
windowPos.X + windowSize.X - popUpSize.X - 10);
}
var resultPosOnWindow = resultPosOnScreen - ImGui.GetWindowPos();
ImGui.SetCursorPos(resultPosOnWindow);
ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 6);
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 14);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(6, 6));
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 6));
ImGui.PushStyleColor(ImGuiCol.ChildBg, UiColors.BackgroundFull.Fade(0.8f).Rgba);
ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, Color.Transparent.Rgba);
// Base size from previous content.
var last = WindowContentExtend.GetLastAndReset()
+ ImGui.GetStyle().WindowPadding * 2
+ new Vector2(10, 3);
// Target: show up to 12 rows on first open if we have enough items.
const int targetVisibleRows = 12;
if (filter.MatchingSymbolUis.Count >= targetVisibleRows)
{
// Ensure _rowHeight is initialized (approximate if needed).
if (_rowHeight <= 0)
{
var lineHeight = ImGui.GetTextLineHeightWithSpacing();
_rowHeight = lineHeight;
}
var desiredHeight = _rowHeight * targetVisibleRows;
// Add top/bottom padding similar to your existing layout.
desiredHeight += ImGui.GetStyle().WindowPadding.Y * 2;
if (desiredHeight > last.Y)
{
last.Y = desiredHeight;
}
}
last.Y = last.Y.Clamp(0, 250);
var resultAreaOnScreen = ImRect.RectWithSize(resultPosOnScreen, last);
bool childOpen = ImGui.BeginChild(
999,
last,
ImGuiChildFlags.Borders | ImGuiChildFlags.AlwaysUseWindowPadding,
ImGuiWindowFlags.NoResize);
if (childOpen)
{
FrameStats.Current.OpenedPopupHovered = ImGui.IsWindowHovered();
if (!string.IsNullOrEmpty(filter.SearchString)
|| filter.FilterInputType != null
|| filter.FilterOutputType != null)
{
result |= DrawSearchResultEntries(context, filter);
}
else
{
result |= SymbolBrowsing.Draw(context);
}
}
ImGui.EndChild();
ImGui.PopStyleColor(2);
ImGui.PopStyleVar(4);
var wasClickedOutside = ImGui.IsMouseClicked(ImGuiMouseButton.Left)
&& !resultAreaOnScreen.Contains(ImGui.GetMousePos());
if (wasClickedOutside)
{
result |= UiResults.ClickedOutside;
}
return result;
}
private static void PrintTypeFilter(SymbolFilter filter)
{
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 UiResults DrawSearchResultEntries(GraphUiContext context, SymbolFilter filter)
{
var result = UiResults.None;
if (ImGui.IsKeyReleased(Key.CursorDown.ToImGuiKey()))
{
UiListHelpers.AdvanceSelectedItem(filter.MatchingSymbolUis!, ref _selectedSymbolUi, 1);
result = UiResults.SelectionChanged;
}
else if (ImGui.IsKeyReleased(Key.CursorUp.ToImGuiKey()))
{
UiListHelpers.AdvanceSelectedItem(filter.MatchingSymbolUis!, ref _selectedSymbolUi, -1);
result = UiResults.SelectionChanged;
}
var gotAMatch = filter.MatchingSymbolUis.Count > 0
&& (_selectedSymbolUi != null && !filter.MatchingSymbolUis.Contains(_selectedSymbolUi));
if (gotAMatch)
_selectedSymbolUi = filter.MatchingSymbolUis[0];
if (_selectedSymbolUi == null && EditorSymbolPackage.AllSymbolUis.Any())
_selectedSymbolUi = EditorSymbolPackage.AllSymbolUis.First();
// --- ImGuiListClipper integration (only when child is visible) ---
var count = filter.MatchingSymbolUis.Count;
if (count > 0)
{
unsafe
{
// Measure one row height once (symbol name text height).
if (_rowHeight <= 0)
{
var size = ImGui.CalcTextSize(filter.MatchingSymbolUis[0].Symbol.Name);
// Add padding similar to the selectable's visual height.
_rowHeight = size.Y + ImGui.GetStyle().FramePadding.Y * 2;
}
ImGuiListClipperPtr clipper =
new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
clipper.Begin(count, _rowHeight);
while (clipper.Step())
{
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
{
var symbolUi = filter.MatchingSymbolUis[i];
result |= DrawSymbolUiEntry(context, symbolUi);
}
}
clipper.End();
}
}
WindowContentExtend.ExtendToLastItem(200);
return result;
}
internal static UiResults DrawSymbolUiEntry(GraphUiContext context, SymbolUi symbolUi, bool showType=true)
{
var result = UiResults.None;
var symbolHash = symbolUi.Symbol.Id.GetHashCode();
ImGui.PushID(symbolHash);
var symbolNamespace = symbolUi.Symbol.Namespace;
var isRelevantNamespace = IsRelevantNamespace(context, symbolNamespace);
var type = symbolUi.Symbol.OutputDefinitions.Count > 0
? symbolUi.Symbol.OutputDefinitions[0]?.ValueType
: null;
TypeUiRegistry.TryGetPropertiesForType(type, out var properties);
var color = properties.Color;
ImGui.PushStyleColor(ImGuiCol.Header, ColorVariations.OperatorBackground.Apply(color).Rgba);
var hoverColor = ColorVariations.OperatorBackgroundHover.Apply(color).Rgba;
hoverColor.W = 0.3f;
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, hoverColor);
ImGui.PushStyleColor(ImGuiCol.Text, ColorVariations.OperatorLabel.Apply(color).Rgba);
var isSelected = symbolUi == _selectedSymbolUi;
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 14);
ImGui.SetNextItemWidth(20);
var size = ImGui.CalcTextSize(symbolUi.Symbol.Name);
if (ImGui.Selectable($"##Selectable{symbolHash}",
isSelected,
ImGuiSelectableFlags.None,
new Vector2(size.X, 0)))
{
result |= UiResults.Create;
_selectedSymbolUi = symbolUi;
}
ImGui.PopStyleVar();
// var dl = ImGui.GetForegroundDrawList();
// dl.AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), Color.Green);
var isHovered = ImGui.IsItemHovered();
if (isHovered)
{
// Let the Help window follow the hovered symbol; its preview replaces the summary tooltip.
HoveredHelpTarget.SetOperator(symbolUi.Symbol.Id);
if (!HelpWindow.HoverPreviewActive)
{
ImGui.SetNextWindowSize(new Vector2(300, 0));
ImGui.BeginTooltip();
OperatorHelp.DrawHelpSummary(symbolUi, false);
ImGui.EndTooltip();
}
}
ImGui.SameLine(ImGui.GetItemRectMin().X - ImGui.GetWindowPos().X);
ImGui.TextUnformatted(symbolUi.Symbol.Name);
if (showType && type != null)
{
ImGui.SameLine(0,10);
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.3f);
ImGui.TextUnformatted(type.Name.ToString());
ImGui.PopStyleVar();
}
ImGui.PopStyleColor(3);
ImGui.PopID();
return result;
}
private static bool IsRelevantNamespace(GraphUiContext context, string symbolNamespace)
{
var projectNamespace = "user." + context.CompositionInstance.Symbol.SymbolPackage.AssemblyInformation.Name + ".";
var compositionNameSpace = context.CompositionInstance.Symbol.Namespace;
var isRelevantNamespace =
symbolNamespace.StartsWith("Lib.")
|| symbolNamespace.StartsWith("Types.")
|| symbolNamespace.StartsWith("Examples.Lib.")
|| symbolNamespace.StartsWith(projectNamespace)
|| symbolNamespace.StartsWith(compositionNameSpace);
return isRelevantNamespace;
}
private static bool _focusInputNextTime = true;
private static SymbolUi? _selectedSymbolUi;
private static ImRect _placeholderAreaOnScreen;
internal static readonly SymbolFilter Filter = new();
private static MagGraphItem? _placeholderItem;
private static MagGraphItem.Directions _connectionOrientation = MagGraphItem.Directions.Horizontal;
// Cached row height for clipper
private static float _rowHeight = 0;
[Flags]
internal enum UiResults
{
None = 1 << 1,
SelectionChanged = 1 << 2,
FilterChanged = 1 << 3,
Create = 1 << 4,
Cancel = 1 << 5,
ClickedOutside = 1 << 6,
}
}