From fe624dcbd50e7aee7c0196ccc4e45e8b9fe13cd1 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Tue, 26 May 2020 22:33:27 -0600 Subject: [PATCH 1/4] new 'DrawFrame' impl --- Terminal.Gui/Core.cs | 47 ++--- Terminal.Gui/Drivers/ConsoleDriver.cs | 237 ++++++++++++++++-------- Terminal.Gui/Views/FrameView.cs | 29 +-- UICatalog/Scenarios/ComputedLayout.cs | 8 +- UICatalog/Scenarios/WindowExperiment.cs | 36 ++-- UICatalog/UICatalog.cs | 4 +- 6 files changed, 220 insertions(+), 141 deletions(-) diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 58767b0b4..e31fe0b5c 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -798,14 +798,14 @@ namespace Terminal.Gui { } // Converts a rectangle in view coordinates to screen coordinates. - Rect RectToScreen (Rect rect) + internal Rect RectToScreen (Rect rect) { ViewToScreen (rect.X, rect.Y, out var x, out var y, clipped: false); return new Rect (x, y, rect.Width, rect.Height); } // Clips a rectangle in screen coordinates to the dimensions currently available on the screen - Rect ScreenClip (Rect rect) + internal Rect ScreenClip (Rect rect) { var x = rect.X < 0 ? 0 : rect.X; var y = rect.Y < 0 ? 0 : rect.Y; @@ -1082,7 +1082,7 @@ namespace Terminal.Gui { /// Constructs. /// /// - public KeyEventEventArgs(KeyEvent ke) => KeyEvent = ke; + public KeyEventEventArgs (KeyEvent ke) => KeyEvent = ke; /// /// The for the event. /// @@ -1683,13 +1683,13 @@ namespace Terminal.Gui { nx = Math.Max (x, 0); nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx; bool m, s; - if (SuperView == null || SuperView.GetType() != typeof(Toplevel)) + if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) m = Application.Top.MenuBar != null; else m = ((Toplevel)SuperView).MenuBar != null; int l = m ? 1 : 0; ny = Math.Max (y, l); - if (SuperView == null || SuperView.GetType() != typeof(Toplevel)) + if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) s = Application.Top.StatusBar != null; else s = ((Toplevel)SuperView).StatusBar != null; @@ -1867,11 +1867,6 @@ namespace Terminal.Gui { return contentView.GetEnumerator (); } - void DrawFrame (bool fill = true) - { - DrawFrame (new Rect (0, 0, Frame.Width, Frame.Height), padding, fill: fill); - } - /// /// Add the specified view to the . /// @@ -1916,30 +1911,24 @@ namespace Terminal.Gui { public override void Redraw (Rect bounds) { Application.CurrentView = this; + var scrRect = RectToScreen (new Rect (0, 0, Frame.Width, Frame.Height)); + var savedClip = Driver.Clip; + Driver.Clip = ScreenClip (RectToScreen (Bounds)); if (NeedDisplay != null && !NeedDisplay.IsEmpty) { - DrawFrameWindow (); + Driver.SetAttribute (ColorScheme.Normal); + Driver.DrawFrame (scrRect, padding, true); } contentView.Redraw (contentView.Bounds); ClearNeedsDisplay (); - DrawFrameWindow (false); + Driver.SetAttribute (ColorScheme.Normal); + Driver.DrawFrame (scrRect, padding, false); - void DrawFrameWindow (bool fill = true) - { - Driver.SetAttribute (ColorScheme.Normal); - DrawFrame (fill); - if (HasFocus) - Driver.SetAttribute (ColorScheme.HotNormal); - var width = Frame.Width - (padding + 2) * 2; - if (Title != null && width > 4) { - Move (1 + padding, padding); - Driver.AddRune (' '); - var str = Title.Length >= width ? Title [0, width - 2] : Title; - Driver.AddStr (str); - Driver.AddRune (' '); - } - Driver.SetAttribute (ColorScheme.Normal); - } + if (HasFocus) + Driver.SetAttribute (ColorScheme.HotNormal); + Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding); + Driver.Clip = savedClip; + Driver.SetAttribute (ColorScheme.Normal); } // @@ -2415,7 +2404,7 @@ namespace Terminal.Gui { if (toplevel.LayoutStyle == LayoutStyle.Computed) toplevel.RelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); toplevel.LayoutSubviews (); - Loaded?.Invoke (null, new ResizedEventArgs () { Rows = Driver.Rows, Cols = Driver.Cols } ); + Loaded?.Invoke (null, new ResizedEventArgs () { Rows = Driver.Rows, Cols = Driver.Cols }); toplevel.WillPresent (); Redraw (toplevel); toplevel.PositionCursor (); diff --git a/Terminal.Gui/Drivers/ConsoleDriver.cs b/Terminal.Gui/Drivers/ConsoleDriver.cs index 232be1c28..d68b5ed4b 100644 --- a/Terminal.Gui/Drivers/ConsoleDriver.cs +++ b/Terminal.Gui/Drivers/ConsoleDriver.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Security.Cryptography; using Mono.Terminal; using NStack; using Unix.Terminal; @@ -525,91 +526,173 @@ namespace Terminal.Gui { TerminalResized = terminalResized; } + // Useful for debugging (e.g. change to `*` + const char clearChar = ' '; + + /// + /// Draws the title for a Window-style view incorporating padding. + /// + /// Screen relative region where the frame will be drawn. + /// The title for the window. The title will only be drawn if title is not null or empty and paddingTop is greater than 0. + /// Number of columns to pad on the left (if 0 the border will not appear on the left). + /// Number of rows to pad on the top (if 0 the border and title will not appear on the top). + /// Number of columns to pad on the right (if 0 the border will not appear on the right). + /// Number of rows to pad on the bottom (if 0 the border will not appear on the bottom). + /// + public void DrawWindowTitle (Rect region, ustring title, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) + { + var width = region.Width - (paddingLeft + 2) * 2; + if (!ustring.IsNullOrEmpty(title) && width > 4) { + + Move (region.X + 1 + paddingLeft, region.Y + paddingTop); + AddRune (' '); + var str = title.Length >= width ? title [0, width - 2] : title; + AddStr (str); + AddRune (' '); + } + } + + /// + /// Draws a frame for a window with padding aand n optional visible border inside the padding. + /// + /// Screen relative region where the frame will be drawn. + /// Number of columns to pad on the left (if 0 the border will not appear on the left). + /// Number of rows to pad on the top (if 0 the border and title will not appear on the top). + /// Number of columns to pad on the right (if 0 the border will not appear on the right). + /// Number of rows to pad on the bottom (if 0 the border will not appear on the bottom). + /// If set to true and any padding dimension is > 0 the border will be drawn. + /// If set to true it will clear the content area (the area inside the padding) with the current color, otherwise the content area will be left untouched. + public void DrawWindowFrame (Rect region, int paddingLeft = 0, int paddingTop = 0, int paddingRight = 0, int paddingBottom = 0, bool border = true, bool fill = false) + { + void AddRuneAt (int col, int row, Rune ch) + { + Move (col, row); + AddRune (ch); + } + + int fwidth = (int)(region.Width - (paddingRight + paddingLeft)); + int fheight = (int)(region.Height - (paddingBottom + paddingTop)); + int fleft = region.X + paddingLeft; + int fright = fleft + fwidth + 1; + int ftop = region.Y + paddingTop; + int fbottom = ftop + fheight + 1; + + Rune hLine = border ? HLine : clearChar; + Rune vLine = border ? VLine : clearChar; + Rune uRCorner = border ? URCorner : clearChar; + Rune uLCorner = border ? ULCorner : clearChar; + Rune lLCorner = border ? LLCorner : clearChar; + Rune lRCorner = border ? LRCorner : clearChar; + + // Outside top + if (paddingTop > 1) { + for (int r = region.Y; r < ftop; r++) { + for (int c = region.X; c <= fright + paddingRight; c++) { + AddRuneAt (c, r, 'T'); + } + } + } + + // Outside top-left + for (int c = region.X; c <= fleft; c++) { + AddRuneAt (c, ftop, 'L'); + } + + // Frame top-left corner + AddRuneAt (fleft, ftop, paddingTop >= 0 ? (paddingLeft >= 0 ? uLCorner : hLine) : clearChar); + + // Frame top + for (int c = fleft + 1; c <= fright; c++) { + AddRuneAt (c, ftop, paddingTop > 0 ? hLine : clearChar); + } + + // Frame top-right corner + if (fright > fleft) { + AddRuneAt (fright, ftop, paddingTop >= 0 ? (paddingRight >= 0 ? uRCorner : hLine) : clearChar); + } + + // Outside top-right corner + for (int c = fright + 1; c < fright + paddingRight; c++) { + AddRuneAt (c, ftop, 'R'); + } + + // Left, Fill, Right + if (fbottom > ftop) { + for (int r = ftop + 1; r < fbottom; r++) { + // Outside left + for (int c = region.X; c < fleft; c++) { + AddRuneAt (c, r, 'L'); + } + + // Frame left + AddRuneAt (fleft, r, paddingLeft > 0 ? vLine : clearChar); + + // Fill + if (fill) { + for (int x = fleft + 1; x < fright; x++) { + AddRuneAt (x, r, clearChar); + } + } + + // Frame right + if (fright > fleft) { + AddRuneAt (fright, r, paddingRight > 0 ? vLine : clearChar); + } + + // Outside right + for (int c = fright + 1; c < fright + paddingRight; c++) { + AddRuneAt (c, r, 'R'); + } + } + + // Outside Bottom + for (int c = region.X; c < fleft; c++) { + AddRuneAt (c, fbottom, 'L'); + } + + // Frame bottom-left + AddRuneAt (fleft, fbottom, paddingLeft > 0 ? lLCorner : clearChar); + + if (fright > fleft) { + // Frame bottom + for (int c = fleft + 1; c < fright; c++) { + AddRuneAt (c, fbottom, paddingBottom > 0 ? hLine : clearChar); + } + + // Frame bottom-right + AddRuneAt (fright, fbottom, paddingRight > 0 ? (paddingBottom > 0 ? lRCorner : hLine) : clearChar); + } + + // Outside right + for (int c = fright + 1; c < fright + paddingRight; c++) { + AddRuneAt (c, fbottom, 'R'); + } + } + + // Out bottom - ensure top is always drawn if we overlap + if (paddingBottom > 0) { + for (int r = fbottom + 1; r < fbottom + paddingBottom; r++) { + for (int c = region.X; c <= fright + paddingRight; c++) { + AddRuneAt (c, r, 'B'); + } + } + } + } + /// /// Draws a frame on the specified region with the specified padding around the frame. /// /// Region where the frame will be drawn.. /// Padding to add on the sides. /// If set to true it will clear the contents with the current color, otherwise the contents will be left untouched. + /// This is a legacy/depcrecated API. Use . + /// A padding value of 0 means there is actually a 1 cell border. public virtual void DrawFrame (Rect region, int padding, bool fill) { - int width = region.Width; - int height = region.Height; - int b; - int fwidth = width - padding * 2; - int fheight = height - 1 - padding; - - Move (region.X, region.Y); - if (padding > 0) { - for (int l = 0; l < padding; l++) - for (b = region.X; b < region.X + width; b++) { - AddRune (' '); - Move (b + 1, region.Y); - } - } - Move (region.X, region.Y + padding); - for (int c = 0; c < padding; c++) { - AddRune (' '); - Move (region.X + 1, region.Y + padding); - } - AddRune (ULCorner); - for (b = region.X; b < region.X + fwidth - 2; b++) { - AddRune (HLine); - Move (b + (padding > 0 ? padding + 2 : 2), region.Y + padding); - } - AddRune (URCorner); - for (int c = 0; c < padding; c++) { - AddRune (' '); - Move (region.X + 1, region.Y + padding); - } - for (b = 1 + padding; b < fheight; b++) { - Move (region.X, region.Y + b); - for (int c = 0; c < padding; c++) { - AddRune (' '); - Move (region.X + 1, region.Y + b); - } - AddRune (VLine); - if (fill) { - for (int x = region.X + 1; x < region.X + fwidth - 1; x++) { - AddRune (' '); - Move (x + (padding > 0 ? padding + 1 : 1), region.Y + b); - } - } else { - if (padding > 0) - Move (region.X + fwidth, region.Y + b); - else - Move (region.X + fwidth - 1, region.Y + b); - } - AddRune (VLine); - for (int c = 0; c < padding; c++) { - AddRune (' '); - Move (region.X + 1, region.Y + b); - } - } - Move (region.X, region.Y + fheight); - for (int c = 0; c < padding; c++) { - AddRune (' '); - Move (region.X + 1, region.Y + b); - } - AddRune (LLCorner); - for (b = region.X; b < region.X + fwidth - 2; b++) { - AddRune (HLine); - Move (b + (padding > 0 ? padding + 2 : 2), region.Y + fheight); - } - AddRune (LRCorner); - for (int c = 0; c < padding; c++) { - AddRune (' '); - Move (region.X + 1, region.Y); - } - if (padding > 0) { - Move (region.X, region.Y + height - padding); - for (int l = 0; l < padding; l++) { - for (b = region.X; b < region.X + width; b++) { - AddRune (' '); - Move (b + 1, region.Y + height - padding); - } - } - } + // DrawFrame assumes the frame is always at least one row/col thick + // DrawWindowFrame assumes a padding of 0 means NO padding + padding++; + DrawWindowFrame (new Rect (region.X - 1, region.Y - 1, region.Width, region.Height), padding, padding, padding, padding, fill: fill); } diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index f5ea13bcf..6768a01ae 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -134,23 +134,26 @@ namespace Terminal.Gui { /// public override void Redraw (Rect bounds) { - if (!NeedDisplay.IsEmpty) { - Driver.SetAttribute (ColorScheme.Normal); - DrawFrame (); - if (HasFocus) - Driver.SetAttribute (ColorScheme.Normal); - var width = Frame.Width; - if (Title != null && width > 4) { - Move (1, 0); - Driver.AddRune (' '); - var str = Title.Length > width ? Title [0, width - 4] : Title; - Driver.AddStr (str); - Driver.AddRune (' '); - } + var padding = 0; + Application.CurrentView = this; + var scrRect = RectToScreen (new Rect (0, 0, Frame.Width, Frame.Height)); + var savedClip = Driver.Clip; + Driver.Clip = ScreenClip (RectToScreen (Bounds)); + + if (NeedDisplay != null && !NeedDisplay.IsEmpty) { Driver.SetAttribute (ColorScheme.Normal); + Driver.DrawFrame (scrRect, padding, true); } contentView.Redraw (contentView.Bounds); ClearNeedsDisplay (); + Driver.SetAttribute (ColorScheme.Normal); + Driver.DrawFrame (scrRect, padding, false); + + if (HasFocus) + Driver.SetAttribute (ColorScheme.HotNormal); + Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding); + Driver.Clip = savedClip; + Driver.SetAttribute (ColorScheme.Normal); } } } diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index d5aecf6e3..17ee66f7d 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -71,10 +71,10 @@ namespace UICatalog { string txt = "Resize the terminal to see computed layout in action."; var labelList = new List