using System; using System.Collections.Generic; using System.Linq; using Spectre.Console.Rendering; namespace Spectre.Console { /// /// A renderable horizontal rule. /// public sealed class Rule : Renderable, IAlignable, IHasBoxBorder { /// /// Gets or sets the rule title markup text. /// public string? Title { get; set; } /// /// Gets or sets the rule style. /// public Style? Style { get; set; } /// /// Gets or sets the rule's title alignment. /// public Justify? Alignment { get; set; } /// public BoxBorder Border { get; set; } = BoxBorder.Square; internal int TitlePadding { get; set; } = 2; internal int TitleSpacing { get; set; } = 1; /// /// Initializes a new instance of the class. /// public Rule() { } /// /// Initializes a new instance of the class. /// /// The rule title markup text. public Rule(string title) { Title = title ?? throw new ArgumentNullException(nameof(title)); } /// protected override IEnumerable Render(RenderContext context, int maxWidth) { var extraLength = (2 * TitlePadding) + (2 * TitleSpacing); if (Title == null || maxWidth <= extraLength) { return GetLineWithoutTitle(context, maxWidth); } // Get the title and make sure it fits. var title = GetTitleSegments(context, Title, maxWidth - extraLength); if (Segment.CellCount(context, title) > maxWidth - extraLength) { // Truncate the title title = Segment.TruncateWithEllipsis(title, context, maxWidth - extraLength); if (!title.Any()) { // We couldn't fit the title at all. return GetLineWithoutTitle(context, maxWidth); } } var (left, right) = GetLineSegments(context, maxWidth, title); var segments = new List(); segments.Add(left); segments.AddRange(title); segments.Add(right); segments.Add(Segment.LineBreak); return segments; } private IEnumerable GetLineWithoutTitle(RenderContext context, int maxWidth) { var border = Border.GetSafeBorder(context.LegacyConsole || !context.Unicode); var text = border.GetPart(BoxBorderPart.Top).Repeat(maxWidth); return new[] { new Segment(text, Style ?? Style.Plain), Segment.LineBreak, }; } private IEnumerable GetTitleSegments(RenderContext context, string title, int width) { title = title.NormalizeNewLines().ReplaceExact("\n", " ").Trim(); var markup = new Markup(title, Style); return ((IRenderable)markup).Render(context.WithSingleLine(), width); } private (Segment Left, Segment Right) GetLineSegments(RenderContext context, int width, IEnumerable title) { var titleLength = Segment.CellCount(context, title); var border = Border.GetSafeBorder(context.LegacyConsole || !context.Unicode); var borderPart = border.GetPart(BoxBorderPart.Top); var alignment = Alignment ?? Justify.Center; if (alignment == Justify.Left) { var left = new Segment(borderPart.Repeat(TitlePadding) + new string(' ', TitleSpacing), Style ?? Style.Plain); var rightLength = width - titleLength - left.CellCount(context) - TitleSpacing; var right = new Segment(new string(' ', TitleSpacing) + borderPart.Repeat(rightLength), Style ?? Style.Plain); return (left, right); } else if (alignment == Justify.Center) { var leftLength = ((width - titleLength) / 2) - TitleSpacing; var left = new Segment(borderPart.Repeat(leftLength) + new string(' ', TitleSpacing), Style ?? Style.Plain); var rightLength = width - titleLength - left.CellCount(context) - TitleSpacing; var right = new Segment(new string(' ', TitleSpacing) + borderPart.Repeat(rightLength), Style ?? Style.Plain); return (left, right); } else if (alignment == Justify.Right) { var right = new Segment(new string(' ', TitleSpacing) + borderPart.Repeat(TitlePadding), Style ?? Style.Plain); var leftLength = width - titleLength - right.CellCount(context) - TitleSpacing; var left = new Segment(borderPart.Repeat(leftLength) + new string(' ', TitleSpacing), Style ?? Style.Plain); return (left, right); } throw new NotSupportedException("Unsupported alignment."); } } }