using System.Text.RegularExpressions;
using ImGuiNET;
using T3.Core.DataTypes.Vector;
using T3.Core.Operator;
using T3.Core.SystemUi;
using T3.Core.Utils;
using T3.Editor.Gui.Input;
using T3.Editor.Gui.UiHelpers;
using T3.Editor.UiModel;
namespace T3.Editor.Gui.Styling;
///
/// A set of special wrappers for ImGui components.
/// Also, checkout the FormInputs class.
///
internal static partial class CustomComponents
{
/// Draw a splitter
///
/// Take from https://github.com/ocornut/imgui/issues/319#issuecomment-147364392
///
public static bool SplitFromBottom(ref float offsetFromBottom)
{
const float thickness = 3;
var hasBeenDragged = false;
var backupPos = ImGui.GetCursorPos();
var windowPos = ImGui.GetWindowPos();
var windowSize = ImGui.GetWindowSize();
var pos = new Vector2(windowPos.X, windowPos.Y + windowSize.Y - offsetFromBottom - thickness - 1);
ImGui.SetCursorScreenPos(pos);
ImGui.PushStyleColor(ImGuiCol.Button, UiColors.BackgroundGaps.Rgba);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UiColors.BackgroundActive.Rgba);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UiColors.BackgroundActive.Rgba);
ImGui.Button("##Splitter", new Vector2(-1, thickness));
ImGui.PopStyleColor(3);
// Disabled for now, since Setting MouseCursor wasn't working reliably
// if (ImGui.IsItemHovered() )
// {
// //ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeNS);
// }
if (ImGui.IsItemActive())
{
if (Math.Abs(ImGui.GetIO().MouseDelta.Y) > 0)
{
hasBeenDragged = true;
offsetFromBottom =
(offsetFromBottom - ImGui.GetIO().MouseDelta.Y)
.Clamp(0, windowSize.Y - thickness);
}
}
ImGui.SetCursorPos(backupPos);
return hasBeenDragged;
}
public static void HelpText(string text)
{
ImGui.PushFont(Fonts.FontSmall);
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba);
ImGui.TextWrapped(text);
ImGui.PopStyleColor();
ImGui.PopFont();
ImGui.Dummy(new Vector2(0, 4 * T3Ui.UiScaleFactor));
}
public static void SmallGroupHeader(string text)
{
FormInputs.AddVerticalSpace(5);
ImGui.PushFont(Fonts.FontSmall);
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba);
ImGui.TextUnformatted(text.ToUpperInvariant());
ImGui.PopStyleColor();
ImGui.PopFont();
FormInputs.AddVerticalSpace(2);
}
public static void MenuGroupHeader(string text)
{
FormInputs.AddVerticalSpace();
ImGui.PushFont(Fonts.FontSmall);
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba);
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetFrameHeight());
ImGui.TextUnformatted(text);
ImGui.PopStyleColor();
ImGui.PopFont();
}
///
/// Uses slightly different styling than ImGui.Separator()
///
public static void SeparatorLine()
{
FormInputs.AddVerticalSpace(4);
var x = ImGui.GetCursorPosX();
ImGui.SetCursorPosX(0);
var p = ImGui.GetCursorScreenPos();
//var p = ImGui.GetCursorStartPos() + ImGui.GetWindowPos() + new Vector2(1,1);
ImGui.GetWindowDrawList()
.AddRectFilled(p,
p + new Vector2(ImGui.GetWindowSize().X, 1), UiColors.ForegroundFull.Fade(0.1f));
FormInputs.AddVerticalSpace(5);
ImGui.SetCursorPosX(x);
}
///
/// A small label that can be used to structure context menus
///
public static void HintLabel(string label)
{
ImGui.PushFont(Fonts.FontSmall);
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.Gray.Rgba);
ImGui.TextUnformatted(label);
ImGui.PopStyleColor();
ImGui.PopFont();
}
public static void FillWithStripes(ImDrawListPtr drawList, ImRect areaOnScreen, float canvasScale, float patternWidth = 16)
{
drawList.PushClipRect(areaOnScreen.Min, areaOnScreen.Max, true);
var lineColor = new Color(0f, 0f, 0f, 0.2f);
var stripeOffset = (patternWidth / 2 * canvasScale);
var lineWidth = stripeOffset / 2.7f;
var h = areaOnScreen.GetHeight();
var stripeCount = (int)((areaOnScreen.GetWidth() + h + 3 * lineWidth) / stripeOffset);
var p = areaOnScreen.Min - new Vector2(h + lineWidth, +lineWidth);
var offset = new Vector2(h + 2 * lineWidth,
h + 2 * lineWidth);
for (var i = 0; i < stripeCount; i++)
{
drawList.AddLine(p, p + offset, lineColor, lineWidth);
p.X += stripeOffset;
}
drawList.PopClipRect();
}
public static bool EmptyWindowMessage(string message, string buttonLabel = null)
{
var center = ImGui.GetWindowPos() + ImGui.GetWindowSize() * 0.5f;
var lines = message.Split('\n').ToArray();
var lineCount = lines.Length;
if (!string.IsNullOrEmpty(buttonLabel))
lineCount++;
var textLineHeight = ImGui.GetTextLineHeight();
var y = center.Y - lineCount * textLineHeight / 2;
var drawList = ImGui.GetWindowDrawList();
var emptyMessageColor = UiColors.TextMuted;
foreach (var line in lines)
{
if (!string.IsNullOrEmpty(line))
{
var textSize = ImGui.CalcTextSize(line);
var position = new Vector2(center.X - textSize.X / 2, y);
drawList.AddText(position, emptyMessageColor, line);
}
y += textLineHeight;
}
if (!string.IsNullOrEmpty(buttonLabel))
{
y += 10;
var style = ImGui.GetStyle();
var textSize = ImGui.CalcTextSize(buttonLabel) + style.FramePadding;
var position = new Vector2(center.X - textSize.X / 2, y);
ImGui.SetCursorScreenPos(position);
return ImGui.Button(buttonLabel);
}
return false;
}
public static bool DrawInputFieldWithPlaceholder(string placeHolderLabel, ref string value, float width = 0, bool showClear = true,
ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags.None)
{
ImGui.PushID(placeHolderLabel.GetHashCode(StringComparison.Ordinal));
var notEmpty = !string.IsNullOrEmpty(value);
var wasNull = value == null;
if (wasNull)
value = string.Empty;
ImGui.SetNextItemWidth(width - FormInputs.ParameterSpacing - (notEmpty ? ImGui.GetFrameHeight() : 0));
var modified = ImGui.InputText("##", ref value, 1000, inputFlags);
if (!modified && wasNull)
value = null;
if (notEmpty)
{
if (showClear)
{
ImGui.SameLine(0, 0);
if (ImGui.Button("×" + "##" + placeHolderLabel))
{
value = null;
modified = true;
}
}
}
else
{
var drawList = ImGui.GetWindowDrawList();
var minPos = ImGui.GetItemRectMin();
var maxPos = ImGui.GetItemRectMax();
drawList.PushClipRect(minPos, maxPos);
drawList.AddText(minPos + new Vector2(8, 5), UiColors.ForegroundFull.Fade(0.25f), placeHolderLabel);
drawList.PopClipRect();
}
ImGui.PopID();
return modified;
}
///
/// Draws a frame that indicates if the current window is focused.
/// This is useful for windows that have window specific keyboard short cuts.
/// Returns true if the window is focused
///
public static void DrawWindowFocusFrame()
{
if (!ImGui.IsWindowFocused())
return;
var min = ImGui.GetWindowPos();
ImGui.GetWindowDrawList().AddRect(min, min + ImGui.GetWindowSize() + new Vector2(0, 0), UiColors.ForegroundFull.Fade(0.2f));
}
public static string HumanReadablePascalCase(string f)
{
return Regex.Replace(f, "(\\B[A-Z])", " $1");
}
public static bool RoundedButton(string id, float width, ImDrawFlags roundedCorners)
{
var size = new Vector2(width, ImGui.GetFrameHeight());
if (width == 0)
size.X = size.Y;
var clicked = ImGui.InvisibleButton(id, size);
var dl = ImGui.GetWindowDrawList();
var color = ImGui.IsItemHovered() ? ImGuiCol.ButtonHovered.GetStyleColor() : ImGuiCol.Button.GetStyleColor();
dl.AddRectFilled(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), color, 7, roundedCorners);
return clicked;
}
private static Vector2 _dragScrollStart;
public static bool IsDragScrolling => _draggedWindowObject != null;
private static object _draggedWindowObject;
public static bool IsAnotherWindowDragScrolling(object windowObject)
{
return _draggedWindowObject != null && _draggedWindowObject != windowObject;
}
public static void HandleDragScrolling(object windowObject)
{
if (_draggedWindowObject == windowObject)
{
if (ImGui.IsMouseReleased(ImGuiMouseButton.Right))
{
_draggedWindowObject = null;
}
if (ImGui.IsMouseDragging(ImGuiMouseButton.Right))
{
var scrollY = _dragScrollStart.Y - ImGui.GetMouseDragDelta(ImGuiMouseButton.Right).Y;
ImGui.SetScrollY(scrollY);
}
return;
}
if (ImGui.IsWindowHovered( ) && !T3Ui.DragFieldWasHoveredLastFrame && ImGui.IsMouseClicked(ImGuiMouseButton.Right))
{
_dragScrollStart = new Vector2(ImGui.GetScrollX(), ImGui.GetScrollY());
_draggedWindowObject = windowObject;
}
}
internal static bool DrawProjectDropdown(ref EditableSymbolProject selectedValue)
{
return FormInputs.AddDropdown(ref selectedValue,
EditableSymbolProject.AllProjects.OrderBy(x => x.DisplayName),
"Project",
x => x.DisplayName,
"Project to edit symbols in.");
}
public static void DrawSymbolCodeContextMenuItem(Symbol symbol)
{
var symbolPackage = symbol.SymbolPackage;
var project = symbolPackage as EditableSymbolProject;
var enabled = project != null;
if (ImGui.MenuItem("Open C# code", enabled))
{
if (!project!.TryOpenCSharpInEditor(symbol))
{
BlockingWindow.Instance.ShowMessageBox($"Failed to open C# code for {symbol.Name}\nCheck the logs for details.", "Error");
}
}
}
public static void StylizedText(string text, ImFontPtr imFont, Color color, bool addPadding = false)
{
ImGui.PushFont(imFont);
ImGui.PushStyleColor(ImGuiCol.Text, color.Rgba);
ImGui.TextUnformatted(text);
ImGui.PopStyleColor();
ImGui.PopFont();
if (addPadding)
ImGui.Dummy(new Vector2(1, 5 * T3Ui.UiScaleFactor));
}
///
/// Draws the subtle rounded hover background used for library tree folders, covering only the
/// clickable label area. Push transparent HeaderHovered/HeaderActive colors before drawing the
/// tree node so ImGui's default header highlight doesn't show through.
///
public static void DrawHoverHighlightOnLastItem()
{
if (!ImGui.IsItemHovered())
return;
ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(),
UiColors.BackgroundActive.Fade(0.2f), 5 * T3Ui.UiScaleFactor);
}
public static void RightAlign(float itemWidth, bool sameLine = true)
{
if(sameLine)
ImGui.SameLine();
var padding = ImGui.GetStyle().WindowPadding.X;
var avail = ImGui.GetContentRegionAvail().X;
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + avail - itemWidth - padding);
}
///
/// A reusable popover/popup component. Draws a trigger button and opens a popup
/// where you can draw any custom content via the action.
///
/// Unique identifier for the popup.
/// Label displayed on the trigger button.
/// Action to draw the popup content. Return true to close the popup.
/// Width of the trigger button. Use 0 for auto-size.
/// True if the popup was just opened this frame.
public static bool DrawPopover(string id, string triggerLabel, Func drawContent, float triggerWidth = 0)
{
var popupId = $"##Popover_{id}";
var wasOpened = false;
if (triggerWidth > 0)
ImGui.SetNextItemWidth(triggerWidth);
if (ImGui.Button(triggerLabel + "##" + id))
{
ImGui.OpenPopup(popupId);
wasOpened = true;
}
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(8 * T3Ui.UiScaleFactor));
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(6 * T3Ui.UiScaleFactor));
if (ImGui.BeginPopup(popupId))
{
var shouldClose = drawContent();
if (shouldClose)
ImGui.CloseCurrentPopup();
ImGui.EndPopup();
}
ImGui.PopStyleVar(2);
return wasOpened;
}
///
/// Overload that uses an Action instead of Func, for content that doesn't need to close programmatically.
///
public static bool DrawPopover(string id, string triggerLabel, Action drawContent, float triggerWidth = 0)
{
return DrawPopover(id, triggerLabel, () =>
{
drawContent();
return false;
}, triggerWidth);
}
}