#nullable enable using ImGuiNET; using T3.Core.DataTypes.Vector; using T3.Editor.Gui.Input; namespace T3.Editor.Gui.Styling; /// /// Shared "sidebar + content panel" layout used by the Settings, Welcome, Project Settings and Render /// windows so they stay visually identical. The window background shows through behind the (unfilled) /// sidebar; the selected entry and the content panel both take , /// so the active tab reads as one connected surface that floats above the darker chrome. /// /// Usage: /// /// NavigationSidebar.BeginLayout(); /// NavigationSidebar.BeginColumn("id"); /// if (NavigationSidebar.Item("Section", active == X)) active = X; /// NavigationSidebar.EndColumn(); /// NavigationSidebar.BeginContentPanel("Section"); /// // section content /// NavigationSidebar.EndContentPanel(); /// /// internal static class NavigationSidebar { internal const float DefaultWidth = 130f; /// /// Paints the dark window background across the whole content area. Call once at the top of the /// window's draw method (before the sidebar / panel children) so the unfilled sidebar and the /// margins around the content panel read as the window background. /// internal static void BeginLayout() { var dl = ImGui.GetWindowDrawList(); dl.AddRectFilled(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), UiColors.BackgroundGaps); } internal static void BeginColumn(string id, float width = DefaultWidth) { ImGui.BeginChild(id, new Vector2(width * T3Ui.UiScaleFactor, 0), ImGuiChildFlags.None, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoBackground); FormInputs.AddVerticalSpace(); } internal static void EndColumn() { ImGui.EndChild(); } /// Draws one navigation entry filling the column width. Returns true when clicked. internal static bool Item(string label, bool isSelected, Icon icon = Icon.None, bool showCheckmark = false) { var scale = T3Ui.UiScaleFactor; var height = ImGui.GetFrameHeight(); var width = ImGui.GetContentRegionAvail().X; var clicked = ImGui.InvisibleButton($"##{label}", new Vector2(width, height)); var isHovered = ImGui.IsItemHovered(); var min = ImGui.GetItemRectMin(); var max = ImGui.GetItemRectMax(); var dl = ImGui.GetWindowDrawList(); // Rounded only on the left so the right edge butts flush against the content panel — the selected // entry and the panel become one continuous surface. if (isSelected) dl.AddRectFilled(min, max, UiColors.WindowBackground, 5 * scale, ImDrawFlags.RoundCornersLeft); else if (isHovered) dl.AddRectFilled(min, max, UiColors.WindowBackground.Fade(0.4f), 5 * scale, ImDrawFlags.RoundCornersLeft); var textColor = isSelected ? UiColors.Text : UiColors.TextMuted; var font = isSelected ? Fonts.FontBold : Fonts.FontNormal; var midY = (min.Y + max.Y) * 0.5f; var iconHalf = Fonts.FontNormal.FontSize * 0.5f; var cursorX = min.X + 10 * scale; if (icon != Icon.None) { Icons.DrawIconAtScreenPosition(icon, new Vector2(cursorX, midY - iconHalf), dl, textColor); cursorX += 20 * scale; } ImGui.PushFont(font); var textSize = ImGui.CalcTextSize(label); dl.AddText(new Vector2(cursorX, midY - textSize.Y * 0.5f), ImGui.GetColorU32(textColor.Rgba), label); ImGui.PopFont(); if (showCheckmark) Icons.DrawIconAtScreenPosition(Icon.Checkmark, new Vector2(max.X - 20 * scale, midY - iconHalf), dl, UiColors.TextMuted); return clicked; } /// /// Begins the content panel to the right of the sidebar: a rounded /// surface with a margin to the window border, inner padding, and a muted section header. /// // Carries the per-window key from BeginContentPanel to EndContentPanel so the drag-scroll state stays // distinct when several sidebar windows are open at once. Begin/End are paired and drawn sequentially. private static object? _contentPanelScrollKey; /// Optional right-aligned content drawn on the header line (e.g. a help /// button). The callback is responsible for its own right-alignment (e.g. CustomComponents.RightAlign). internal static void BeginContentPanel(string title, object? scrollKey = null, Action? drawHeaderTools = null) { _contentPanelScrollKey = scrollKey; var scale = T3Ui.UiScaleFactor; var margin = 3 * scale; // No gap on the left: the panel sits flush against the selected sidebar entry. ImGui.SameLine(0, 0); ImGui.PushStyleColor(ImGuiCol.ChildBg, UiColors.WindowBackground.Rgba); ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 8 * scale); ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(16, 12) * scale); ImGui.BeginChild("##contentPanel", new Vector2(-margin, -margin), ImGuiChildFlags.AlwaysUseWindowPadding | ImGuiChildFlags.Borders); if (!string.IsNullOrEmpty(title)) { ImGui.PushFont(Fonts.FontLarge); ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba); ImGui.TextUnformatted(title); ImGui.PopStyleColor(); ImGui.PopFont(); drawHeaderTools?.Invoke(); FormInputs.AddVerticalSpace(8); } } internal static void EndContentPanel() { if (_contentPanelScrollKey != null) { CustomComponents.HandleDragScrolling(_contentPanelScrollKey); _contentPanelScrollKey = null; } ImGui.EndChild(); ImGui.PopStyleVar(2); ImGui.PopStyleColor(); } }