using ImGuiNET; using T3.Core.DataTypes.Vector; using T3.Core.Utils; using T3.Editor.App; using T3.SystemUi; namespace T3.Editor.Gui.Styling; internal static partial class CustomComponents { public static bool ToggleButton(ref bool isSelected, string label, Vector2 size, bool trigger = false) { var wasSelected = isSelected; var clicked = false; if (isSelected) { ImGui.PushStyleColor(ImGuiCol.Button, isSelected ? UiColors.BackgroundActive.Fade(0.7f).Rgba : UiColors.BackgroundButton.Rgba); ImGui.PushStyleColor(ImGuiCol.ButtonHovered, isSelected ? UiColors.BackgroundActive.Rgba : UiColors.BackgroundButton.Fade(0.7f).Rgba); ImGui.PushStyleColor(ImGuiCol.Text, UiColors.Text.Rgba); ImGui.PushStyleColor(ImGuiCol.ButtonActive, isSelected ? UiColors.BackgroundActive.Fade(0.7f).Rgba : UiColors.BackgroundButton.Fade(0.7f).Rgba); } if (ImGui.Button(label, size) || trigger) { isSelected = !isSelected; clicked = true; } if (wasSelected) { ImGui.PopStyleColor(4); } return clicked; } public static bool ToggleIconButton(ref bool isSelected, Icon icon, Vector2 size, ButtonStates activeState = ButtonStates.Activated) { var state = isSelected ? activeState : ButtonStates.Default; var clicked = IconButton(icon, size, state); if (clicked) isSelected = !isSelected; return clicked; } public static bool ToggleTwoIconsButton(ref bool isOn, Icon iconOff, Icon iconOn, ButtonStates stateIfOn = ButtonStates.Activated, ButtonStates stateIfOff = ButtonStates.Activated, bool isEnabled = true, bool noBackground= false) { var state = !isEnabled ? ButtonStates.Disabled : isOn ? stateIfOn : stateIfOff; var clicked = noBackground ? TransparentIconButton(isOn ? iconOn : iconOff, Vector2.Zero, state) : IconButton(isOn ? iconOn : iconOff, Vector2.Zero, state); if (clicked && isEnabled) isOn = !isOn; return clicked; } public static bool AddSegmentedIconButton(ref T selectedValue, List icons) where T : struct, Enum { var modified = false; var selectedValueString = selectedValue.ToString(); var isFirst = true; var enums = Enum.GetValues(); for (var index = 0; index < enums.Length; index++) { var icon = icons[index]; var value = enums[index]; if (!isFirst) { ImGui.SameLine(); } var isSelected = selectedValueString == value.ToString(); var clicked = ToggleIconButton(ref isSelected, icon, Vector2.Zero); if (clicked) { modified = true; selectedValue = value; } if (isSelected) { var min = ImGui.GetItemRectMin(); var max = ImGui.GetItemRectMax(); var drawList = ImGui.GetWindowDrawList(); drawList.AddRectFilled(new Vector2(min.X - 2, max.Y), new Vector2(max.X + 2, max.Y + 2), UiColors.StatusActivated); } isFirst = false; } return modified; } /// /// Kept for call-site compatibility — is /// already transparent, so this just forwards to it. /// public static bool TransparentIconButton(Icon icon, Vector2 size, ButtonStates state = ButtonStates.Default) { return IconButton(icon, size, state); } /// /// Toggleable filter chip: all-caps small text, rounded pill, distinct /// colors for active vs. inactive. Returns true on click — caller toggles /// the active flag. Designed for tag-style filter rows (test runner Pick /// state, Console log severity filter, …). /// public static bool TagFilterToggle(string label, bool active) { ImGui.PushFont(Fonts.FontSmall); ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 8 * T3Ui.UiScaleFactor); ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8 * T3Ui.UiScaleFactor, 2 * T3Ui.UiScaleFactor)); if (active) { ImGui.PushStyleColor(ImGuiCol.Button, UiColors.StatusActivated.Rgba); ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UiColors.StatusActivated.Fade(0.85f).Rgba); ImGui.PushStyleColor(ImGuiCol.ButtonActive, UiColors.StatusActivated.Rgba); ImGui.PushStyleColor(ImGuiCol.Text, UiColors.ForegroundFull.Rgba); } else { ImGui.PushStyleColor(ImGuiCol.Button, UiColors.BackgroundButton.Rgba); ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UiColors.BackgroundHover.Rgba); ImGui.PushStyleColor(ImGuiCol.ButtonActive, UiColors.BackgroundButtonActivated.Rgba); ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba); } var clicked = ImGui.Button(label.ToUpperInvariant()); ImGui.PopStyleColor(4); ImGui.PopStyleVar(2); ImGui.PopFont(); return clicked; } /// /// The canonical tool icon: transparent background with a subtle rounded hover, so it stays /// quiet in toolbars and panels. draws a filled /// background to read as "on". Icon tint comes from . /// public static bool IconButton(Icon icon, Vector2 size, ButtonStates state = ButtonStates.Default) { if (size == Vector2.Zero) size = new Vector2(ImGui.GetFrameHeight()); var isActivated = state == ButtonStates.Activated; // A disabled icon isn't meant to be clicked, so it gets no hover feedback (tooltip still shows). PushToolIconStyle(GetRestingIconBackground(isActivated), showHover: state != ButtonStates.Disabled); ImGui.PushID((int)icon); var clicked = ImGui.Button("##iconBtn", size); ImGui.PopID(); var isHovered = ImGui.IsItemHovered() && state == ButtonStates.Default; Icons.DrawIconOnLastItem(icon, isHovered? UiColors.ForegroundFull: GetStateColor(state).Rgba); PopToolIconStyle(); return clicked; } /// /// Transparent tool icon with an explicit tint (used where the color isn't a /// , e.g. type-colored or status-colored icons). /// public static bool IconButton(Icon icon, Vector2 size, Color color) { if (size == Vector2.Zero) size = new Vector2(ImGui.GetFrameHeight()); PushToolIconStyle(GetRestingIconBackground(isActivated: false)); ImGui.PushID((int)icon); var clicked = ImGui.Button("##iconBtn", size); ImGui.PopID(); Icons.DrawIconOnLastItem(icon, color.Rgba); PopToolIconStyle(); return clicked; } /// /// Within this scope tool icons keep a filled background so a toolbar's buttons tile into a /// continuous bar shape. Wrap the toolbar's icon cluster and /// afterwards. Everywhere else tool icons stay transparent. /// public static void PushToolbarIconBackground() => _useFilledIconBackground = true; public static void PopToolbarIconBackground() => _useFilledIconBackground = false; private static Color GetRestingIconBackground(bool isActivated) { if (isActivated) return UiColors.BackgroundButtonActivated; return _useFilledIconBackground ? UiColors.BackgroundButton : Color.Transparent; } private static void PushToolIconStyle(Color background, bool showHover = true) { ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); // Square corners in toolbars so filled buttons tile into a continuous bar; rounded elsewhere. ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, _useFilledIconBackground ? 0 : 4 * T3Ui.UiScaleFactor); ImGui.PushStyleColor(ImGuiCol.Button, background.Rgba); // A muted hover for transparent icons (the regular button-hover is too dominant under // one); toolbar buttons keep the stronger theme hover to stay button-like. Color hover; if (!showHover) hover = background; else if (_useFilledIconBackground) hover = UiColors.BackgroundHover; else hover = UiColors.BackgroundButton.Fade(0.5f); ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hover.Rgba); ImGui.PushStyleColor(ImGuiCol.ButtonActive, showHover ? UiColors.BackgroundButtonActivated.Rgba : background.Rgba); } private static void PopToolIconStyle() { ImGui.PopStyleColor(3); ImGui.PopStyleVar(2); } private static bool _useFilledIconBackground; /// /// An override that allows to pass rounded corner flags to have segmented buttons rounded on the outer edges. /// public static bool RoundedIconButton(string id, Icon icon, float width, ImDrawFlags corners = ImDrawFlags.RoundCornersNone, ButtonStates state = ButtonStates.Emphasized, bool triggered = false) { var iconColor = GetStateColor(state); var size = new Vector2(width, ImGui.GetFrameHeight()); if (width == 0) size.X = size.Y; triggered |= ImGui.InvisibleButton(id, size); var dl = ImGui.GetWindowDrawList(); dl.AddRectFilled(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), GetButtonStateBackgroundColor(), 7, corners); Icons.DrawIconOnLastItem(icon, iconColor); return triggered; } public static bool StateButton(string label, ButtonStates state = ButtonStates.Emphasized) { ImGui.PushStyleColor(ImGuiCol.ButtonActive, UiColors.BackgroundButtonActivated.Rgba); if (state != ButtonStates.Emphasized) { ImGui.PushStyleColor(ImGuiCol.Text, GetStateColor(state).Rgba); if (state == ButtonStates.Activated) { ImGui.PushStyleColor(ImGuiCol.Button, UiColors.BackgroundButtonActivated.Rgba); ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UiColors.BackgroundButtonActivated.Fade(0.8f).Rgba); } } ImGui.AlignTextToFramePadding(); var clicked = ImGui.Button(label); if (state != ButtonStates.Emphasized) ImGui.PopStyleColor(); if (state == ButtonStates.Activated) ImGui.PopStyleColor(2); ImGui.PopStyleColor(1); return clicked; } private static Color GetStateColor(ButtonStates state) { return state switch { ButtonStates.Default => UiColors.TextMuted.Fade(0.8f), ButtonStates.Emphasized => UiColors.Text, ButtonStates.Disabled => UiColors.TextDisabled.Fade(0.6f), ButtonStates.Activated => UiColors.StatusActivated, ButtonStates.NeedsAttention => UiColors.StatusAttention, _ => UiColors.TextMuted }; } private static Color GetButtonStateBackgroundColor() { Color backgroundColor; if (ImGui.IsItemActive()) { backgroundColor = ImGuiCol.ButtonActive.GetStyleColor(); } else if (ImGui.IsItemHovered()) { backgroundColor = ImGuiCol.ButtonHovered.GetStyleColor(); } else { backgroundColor = ImGuiCol.Button.GetStyleColor(); } return backgroundColor; } public static bool DisablableButton(string label, bool isEnabled, bool enableTriggerWithReturn = false) { if (isEnabled) { ImGui.PushFont(Fonts.FontBold); if (ImGui.Button(label) || (enableTriggerWithReturn && ImGui.IsKeyPressed(Key.Return.ToImGuiKey()))) { ImGui.PopFont(); return true; } ImGui.PopFont(); } else { ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(0.3f, 0.3f, 0.3f, 0.1f)); ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1, 1, 1, 0.15f)); ImGui.Button(label); ImGui.PopStyleColor(2); } return false; } internal static Vector2 GetCtaButtonSize(string label, Icon icon = Icon.None) { var showIcon = icon != Icon.None; //var padding = new Vector2(10, 2); ImGui.PushFont(Fonts.FontLarge); var size = ImGui.CalcTextSize(label) + CtaButtonPadding * 2; if (showIcon) size.X += Icons.FontSize; ImGui.PopFont(); return size; } private static Vector2 CtaButtonPadding => new Vector2(10, 2); internal static bool DrawCtaButton(string label, Icon icon, Color textColor, Color bgColor, Color borderColor) { var size = GetCtaButtonSize(label, icon); var clicked = ImGui.InvisibleButton(label, size); var min = ImGui.GetItemRectMin(); var max = ImGui.GetItemRectMax(); var dl = ImGui.GetWindowDrawList(); var isHovered = ImGui.IsItemHovered(); dl.AddRectFilled(min, max, bgColor.Fade(isHovered ? 0.8f : 1f), 5); dl.AddRect(min, max, borderColor, 5); dl.AddText(Fonts.FontLarge, Fonts.FontLarge.FontSize, min + CtaButtonPadding, textColor, label); var screenPos = new Vector2(max.X - Icons.FontSize - CtaButtonPadding.X / 2, (max.Y + min.Y) / 2f - Icons.FontSize / 2f + 1 ).Floor(); if (icon != Icon.None) Icons.DrawIconAtScreenPosition(icon, screenPos, dl, textColor); return clicked; } internal static bool DrawCtaButton(string label, Icon icon = Icon.None, ButtonStates state = ButtonStates.Emphasized) { var textColor = UiColors.Text; var bgColor = UiColors.BackgroundButton; var borderColor = Color.Transparent; switch (state) { case ButtonStates.Activated: textColor = UiColors.ForegroundFull; bgColor = UiColors.BackgroundActive; borderColor = Color.Transparent; break; case ButtonStates.Default: textColor = UiColors.Text; bgColor = Color.Transparent; borderColor = UiColors.ForegroundFull.Fade(0.3f); break; case ButtonStates.Disabled: textColor = UiColors.Text.Fade(0.15f); bgColor = UiColors.BackgroundButton.Fade(0.2f); borderColor = Color.Transparent; break; } return DrawCtaButton(label, icon, textColor, bgColor, borderColor); } /// /// Primary action button for dialogs — CTA styling with a gated disabled state. /// internal static bool DrawCtaButton(string label, bool isEnabled, bool enableTriggerWithReturn = false, Icon icon = Icon.None) { var clicked = DrawCtaButton(label, icon, isEnabled ? ButtonStates.Activated : ButtonStates.Disabled); if (!isEnabled) return false; return clicked || (enableTriggerWithReturn && ImGui.IsKeyPressed(Key.Return.ToImGuiKey())); } public enum ButtonStates { /// Slightly faded — the default look for tool icons; contrasts with Emphasized and Activated. Default, /// Slightly brighter, to emphasize a single icon. Emphasized, /// Barely visible and not meant to be clicked, but its tooltip still shows. Disabled, /// Rare, but currently active — e.g. "has project settings", "show gizmos", "loop mode", console auto-scroll. Activated, /// Temporarily important and easy to overlook — e.g. "audio muted", "currently rendering". A remove icon only needs this on hover. NeedsAttention, } /// /// Draws an search match underline under the last search item /// public static void DrawSearchMatchUnderline(string searchString, ReadOnlySpan strId, Vector2 offset) { if (string.IsNullOrEmpty(searchString)) return; var start = strId.IndexOf(searchString, StringComparison.OrdinalIgnoreCase); if (start == -1) return; var span = strId.Slice(start, searchString.Length); var sizeMatch = ImGui.CalcTextSize(span); var sizeBefore = start > 0 ? ImGui.CalcTextSize(strId[..start]) : Vector2.Zero; var fontSize = ImGui.GetFontSize(); var min = //ImGui.GetItemRectMin() offset + new Vector2( sizeBefore.X, fontSize) ; ImGui.GetWindowDrawList().AddLine(min, min + new Vector2(sizeMatch.X,0), UiColors.BackgroundActive); } }