namespace Terminal.Gui; /// The Border for a . /// /// /// Renders a border around the view with the . A border using /// will be drawn on the sides of that are greater than zero. /// /// /// The of will be drawn based on the value of /// : /// /// /// If 1: /// /// ┌┤1234├──┐ /// │ │ /// └────────┘ /// /// /// /// If 2: /// /// ┌────┐ /// ┌┤1234├──┐ /// │ │ /// └────────┘ /// /// /// /// If 3: /// /// ┌────┐ /// ┌┤1234├──┐ /// │└────┘ │ /// │ │ /// └────────┘ /// /// /// /// See the class. /// public class Border : Adornment { private LineStyle? _lineStyle; /// public Border () { /* Do nothing; A parameter-less constructor is required to support all views unit tests. */ } /// public Border (View parent) : base (parent) { /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */ } /// /// The color scheme for the Border. If set to , gets the /// scheme. color scheme. /// public override ColorScheme ColorScheme { get { if (base.ColorScheme is { }) { return base.ColorScheme; } return Parent?.ColorScheme; } set { base.ColorScheme = value; Parent?.SetNeedsDisplay (); } } /// /// Sets the style of the border by changing the . This is a helper API for setting the /// to (1,1,1,1) and setting the line style of the views that comprise the border. If /// set to no border will be drawn. /// public LineStyle LineStyle { get { if (_lineStyle.HasValue) { return _lineStyle.Value; } // TODO: Make Border.LineStyle inherit from the SuperView hierarchy // TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates // TODO: all this. return Parent.SuperView?.BorderStyle ?? LineStyle.None; } set => _lineStyle = value; } /// public override void OnDrawContent (Rectangle contentArea) { base.OnDrawContent (contentArea); if (Thickness == Thickness.Empty) { return; } //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal); Rectangle screenBounds = BoundsToScreen (contentArea); //OnDrawSubviews (bounds); // TODO: v2 - this will eventually be two controls: "BorderView" and "Label" (for the title) // The border adornment (and title) are drawn at the outermost edge of border; // For Border // ...thickness extends outward (border/title is always as far in as possible) // PERF: How about a call to Rectangle.Offset? Rectangle borderBounds = new ( screenBounds.X + Math.Max (0, Thickness.Left - 1), screenBounds.Y + Math.Max (0, Thickness.Top - 1), Math.Max ( 0, screenBounds.Width - Math.Max ( 0, Math.Max (0, Thickness.Left - 1) + Math.Max (0, Thickness.Right - 1) ) ), Math.Max ( 0, screenBounds.Height - Math.Max ( 0, Math.Max (0, Thickness.Top - 1) + Math.Max (0, Thickness.Bottom - 1) ) ) ); int topTitleLineY = borderBounds.Y; int titleY = borderBounds.Y; var titleBarsLength = 0; // the little vertical thingies int maxTitleWidth = Math.Max ( 0, Math.Min ( Parent.TitleTextFormatter.FormatAndGetSize ().Width, Math.Min (screenBounds.Width - 4, borderBounds.Width - 4) ) ); Parent.TitleTextFormatter.Size = new (maxTitleWidth, 1); int sideLineLength = borderBounds.Height; bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 }; if (!string.IsNullOrEmpty (Parent?.Title)) { if (Thickness.Top == 2) { topTitleLineY = borderBounds.Y - 1; titleY = topTitleLineY + 1; titleBarsLength = 2; } // ┌────┐ //┌┘View└ //│ if (Thickness.Top == 3) { topTitleLineY = borderBounds.Y - (Thickness.Top - 1); titleY = topTitleLineY + 1; titleBarsLength = 3; sideLineLength++; } // ┌────┐ //┌┘View└ //│ if (Thickness.Top > 3) { topTitleLineY = borderBounds.Y - 2; titleY = topTitleLineY + 1; titleBarsLength = 3; sideLineLength++; } } if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) { Parent.TitleTextFormatter.Draw ( new (borderBounds.X + 2, titleY, maxTitleWidth, 1), Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (), Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetHotNormalColor ()); } if (canDrawBorder && LineStyle != LineStyle.None) { LineCanvas lc = Parent?.LineCanvas; bool drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height > 1; bool drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0); bool drawBottom = Thickness.Bottom > 0 && Frame.Width > 1; bool drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0); Attribute prevAttr = Driver.GetAttribute (); if (ColorScheme is { }) { Driver.SetAttribute (GetNormalColor ()); } else { Driver.SetAttribute (Parent.GetNormalColor ()); } if (drawTop) { // ╔╡Title╞═════╗ // ╔╡╞═════╗ if (borderBounds.Width < 4 || string.IsNullOrEmpty (Parent?.Title)) { // ╔╡╞╗ should be ╔══╗ lc.AddLine ( new (borderBounds.Location.X, titleY), borderBounds.Width, Orientation.Horizontal, LineStyle, Driver.GetAttribute () ); } else { // ┌────┐ //┌┘View└ //│ if (Thickness.Top == 2) { lc.AddLine ( new (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute () ); } // ┌────┐ //┌┘View└ //│ if (borderBounds.Width >= 4 && Thickness.Top > 2) { lc.AddLine ( new (borderBounds.X + 1, topTitleLineY), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute () ); lc.AddLine ( new (borderBounds.X + 1, topTitleLineY + 2), Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute () ); } // ╔╡Title╞═════╗ // Add a short horiz line for ╔╡ lc.AddLine ( new (borderBounds.Location.X, titleY), 2, Orientation.Horizontal, LineStyle, Driver.GetAttribute () ); // Add a vert line for ╔╡ lc.AddLine ( new (borderBounds.X + 1, topTitleLineY), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute () ); // Add a vert line for ╞ lc.AddLine ( new ( borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, topTitleLineY ), titleBarsLength, Orientation.Vertical, LineStyle.Single, Driver.GetAttribute () ); // Add the right hand line for ╞═════╗ lc.AddLine ( new ( borderBounds.X + 1 + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2) - 1, titleY ), borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2), Orientation.Horizontal, LineStyle, Driver.GetAttribute () ); } } if (drawLeft) { lc.AddLine ( new (borderBounds.Location.X, titleY), sideLineLength, Orientation.Vertical, LineStyle, Driver.GetAttribute () ); } if (drawBottom) { lc.AddLine ( new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1), borderBounds.Width, Orientation.Horizontal, LineStyle, Driver.GetAttribute () ); } if (drawRight) { lc.AddLine ( new (borderBounds.X + borderBounds.Width - 1, titleY), sideLineLength, Orientation.Vertical, LineStyle, Driver.GetAttribute () ); } Driver.SetAttribute (prevAttr); // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.Ruler)) { // Top var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal }; if (drawTop) { hruler.Draw (new (screenBounds.X, screenBounds.Y)); } // Redraw title if (drawTop && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) { Parent.TitleTextFormatter.Draw ( new (borderBounds.X + 2, titleY, maxTitleWidth, 1), Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (), Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ()); } //Left var vruler = new Ruler { Length = screenBounds.Height - 2, Orientation = Orientation.Vertical }; if (drawLeft) { vruler.Draw (new (screenBounds.X, screenBounds.Y + 1), 1); } // Bottom if (drawBottom) { hruler.Draw (new (screenBounds.X, screenBounds.Y + screenBounds.Height - 1)); } // Right if (drawRight) { vruler.Draw (new (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1); } } } //base.OnDrawContent (contentArea); } }