From 1d7804805dccc1727cda9fcb3ecc2061e0abaa68 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Mon, 24 Apr 2023 16:36:49 -0700 Subject: [PATCH 01/18] Use LineCanvas to draw TableView borders --- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 65 +++ Terminal.Gui/Views/TableView/TableView.cs | 409 +++++++++---------- UICatalog/Scenarios/TableEditor.cs | 12 +- 3 files changed, 271 insertions(+), 215 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 8ba0f956a5..f8a767db64 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -1040,6 +1040,11 @@ public Rect Clip { /// public Rune BottomTee = '\u2534'; + /// + /// The crosshair. + /// + public Rune Crosshair = '\u253c'; + /// /// Checkmark. /// @@ -1085,6 +1090,66 @@ public Rect Clip { /// public Rune UpArrow = '\u25b2'; + /// + /// Up Down Arrow. + /// + public Rune UpDownArrow = '\u2195'; + + /// + /// Up Down Arrow. + /// + public Rune LeftRightArrow = '\u2194'; + + /// + /// Right Double Arrow. + /// + public Rune DoubleRightArrow = '\u21d2'; + + /// + /// Left Double Arrow. + /// + public Rune DoubleLeftArrow = '\u21d0'; + + /// + /// Down Double Arrow. + /// + public Rune DoubleDownArrow = '\u21d3'; + + /// + /// Up Double Arrow. + /// + public Rune DoubleUpArrow = '\u21d1'; + + /// + /// Up Down Double Arrow. + /// + public Rune DoubleUpDownArrow = '\u21d5'; + + /// + /// Left Right Double Arrow. + /// + public Rune DoubleLeftRightArrow = '\u21d4'; + + /// + /// Right Dashed Arrow. + /// + public Rune DashedRightArrow = '\u21e2'; + + /// + /// Left Dashed Arrow. + /// + public Rune DashedLeftArrow = '\u21e0'; + + /// + /// Down Dashed Arrow. + /// + public Rune DashedDownArrow = '\u21e3'; + + /// + /// Up Dashed Arrow. + /// + public Rune DashedUpArrow = '\u21e1'; + /// /// Left indicator for default action (e.g. for ). /// diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index e4f3ebc0c0..14ac26ff0f 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -15,6 +15,7 @@ namespace Terminal.Gui { /// public partial class TableView : View { + private LineCanvas grid = new LineCanvas (); private int columnOffset; private int rowOffset; private int selectedRow; @@ -234,9 +235,6 @@ public override void Redraw (Rect bounds) Move (0, 0); var frame = Frame; - scrollRightPoint = null; - scrollLeftPoint = null; - // What columns to render at what X offset in viewport var columnsToRender = CalculateViewport (bounds).ToArray (); @@ -245,51 +243,101 @@ public override void Redraw (Rect bounds) //invalidate current row (prevents scrolling around leaving old characters in the frame Driver.AddStr (new string (' ', bounds.Width)); - int line = 0; + if (Table == null || columnsToRender.Length < 1) { + return; + } - if (ShouldRenderHeaders ()) { - // Render something like: - /* - ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐ - │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│ - └────────────────────┴──────────┴───────────┴──────────────┴─────────┘ - */ - if (Style.ShowHorizontalHeaderOverline) { - RenderHeaderOverline (line, bounds.Width, columnsToRender); - line++; + var lastCol = columnsToRender [columnsToRender.Length - 1]; + var width = bounds.Width; + if (!Style.ExpandLastColumn) { + width = lastCol.X + lastCol.Width; + } + + // render the cell lines + grid.Clear (); + if (Style.LineColor.Initialized) + Driver.SetAttribute (Style.LineColor); + else + Driver.SetAttribute (this.ColorScheme.Normal); + RenderCellLines (width, Table.Rows.Count, columnsToRender); + + foreach (var p in grid.GetMap (bounds)) { + this.AddRune (p.Key.X, p.Key.Y, p.Value); + } + + // render arrows + scrollRightPoint = null; + scrollLeftPoint = null; + int hh = GetHeaderHeightIfAny (); + if (Style.ShowHorizontalScrollIndicators) { + if (hh > 0 && MoreColumnsToLeft ()) { + scrollLeftPoint = new Point (0, hh); } + if (hh > 0 && MoreColumnsToRight (columnsToRender)) { + scrollRightPoint = new Point (width - 1, hh); + } + } + if (scrollLeftPoint != null) { + AddRuneAt (Driver, 0, scrollLeftPoint.Value.Y - 1, Driver.LeftArrow); + } + if (scrollRightPoint != null) { + AddRuneAt (Driver, scrollRightPoint.Value.X, scrollRightPoint.Value.Y - 1, Driver.RightArrow); + } - if (Style.ShowHeaders) { - RenderHeaderMidline (line, columnsToRender); - line++; + // render the header contents + if (Style.ShowHeaders && hh > 0) { + var padChar = ' '; + if (Style.ShowHorizontalHeaderThroughline && + (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { + padChar = (char)Driver.HLine; } + var yh = hh - 1; if (Style.ShowHorizontalHeaderUnderline) { - RenderHeaderUnderline (line, bounds.Width, columnsToRender); - line++; + yh--; } - } - int headerLinesConsumed = line; + for (var i = 0; i < columnsToRender.Length; i++) { + + var current = columnsToRender [i]; + + var colStyle = Style.GetColumnStyleIfAny (current.Column); + var colName = current.Column.ColumnName; - //render the cells - for (; line < frame.Height; line++) { + //RenderSeparator (current.X - 1, yh, true); - ClearLine (line, bounds.Width); + Move (current.X, yh); + + Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle, padChar)); + + /* + if (Style.ExpandLastColumn == false && current.IsVeryLast) { + RenderSeparator (current.X + current.Width - 1, yh, true); + } + */ + } + } + + // render the cell contents + for (var line = hh; line < frame.Height; line++) { + + //ClearLine (line, width); //work out what Row to render - var rowToRender = RowOffset + (line - headerLinesConsumed); + var rowToRender = RowOffset + (line - hh); //if we have run off the end of the table if (TableIsNullOrInvisible () || rowToRender < 0) continue; // No more data - if(rowToRender >= Table.Rows.Count) { + if (rowToRender >= Table.Rows.Count) { - if(rowToRender == Table.Rows.Count && Style.ShowHorizontalBottomline) { - RenderBottomLine (line, bounds.Width, columnsToRender); + /* + if (rowToRender == Table.Rows.Count && Style.ShowHorizontalBottomline) { + RenderBottomLine (line, width, columnsToRender); } + */ continue; } @@ -335,86 +383,91 @@ private int GetHeaderHeight () return heightRequired; } - private void RenderHeaderOverline (int row, int availableWidth, ColumnToRender [] columnsToRender) + private void RenderCellLines (int width, int height, ColumnToRender [] columnsToRender) { - // Renders a line above table headers (when visible) like: - // ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐ - - for (int c = 0; c < availableWidth; c++) { + var row = 0; + int hh = GetHeaderHeightIfAny (); - var rune = Driver.HLine; - - if (Style.ShowVerticalHeaderLines) { + // First render the header, something like: + /* + ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐ + │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│ + └────────────────────┴──────────┴───────────┴──────────────┴─────────┘ + */ - if (c == 0) { - rune = Driver.ULCorner; - } - // if the next column is the start of a header - else if (columnsToRender.Any (r => r.X == c + 1)) { - rune = Driver.TopTee; - } else if (c == availableWidth - 1) { - rune = Driver.URCorner; - } - // if the next console column is the lastcolumns end - else if (Style.ExpandLastColumn == false && - columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { - rune = Driver.TopTee; + if (hh > 0) { + if (Style.ShowHorizontalHeaderOverline) { + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.OuterHeaderLineStyle); + row++; + } + if (Style.ShowHeaders) { + var lineStyle = Style.InnerHeaderLineStyle; + if (!Style.ShowHorizontalHeaderOverline) + lineStyle = Style.OuterHeaderLineStyle; + if (Style.ShowHorizontalHeaderThroughline && + (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, lineStyle); } + row++; + } + if (Style.ShowHorizontalHeaderUnderline) { + var lineStyle = Style.InnerHeaderLineStyle; + if (!Style.ShowHorizontalHeaderOverline && !Style.ShowHeaders) + lineStyle = Style.OuterHeaderLineStyle; + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, lineStyle); + row++; } - AddRuneAt (Driver, c, row, rune); + if (row > 1 && Style.ShowVerticalHeaderLines) { + foreach (var col in columnsToRender) { + var lineStyle = Style.InnerHeaderLineStyle; + if (col.X - 1 == 0) { + lineStyle = Style.OuterHeaderLineStyle; + } + grid.AddLine (new Point (col.X - 1, 0), row, Orientation.Vertical, lineStyle); + } + grid.AddLine (new Point (width - 1, 0), row, Orientation.Vertical, Style.OuterHeaderLineStyle); + } } - } - - private void RenderHeaderMidline (int row, ColumnToRender [] columnsToRender) - { - // Renders something like: - // │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│ - - ClearLine (row, Bounds.Width); - //render start of line - if (style.ShowVerticalHeaderLines) - AddRune (0, row, Driver.VLine); - - for (int i = 0; i < columnsToRender.Length; i++) { - - var current = columnsToRender [i]; - - var colStyle = Style.GetColumnStyleIfAny (current.Column); - var colName = current.Column.ColumnName; - - RenderSeparator (current.X - 1, row, true); - - Move (current.X, row); - - Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle)); + if (Style.ShowHorizontalBottomline) { + height++; + } - if (Style.ExpandLastColumn == false && current.IsVeryLast) { - RenderSeparator (current.X + current.Width - 1, row, true); + // render the vertical cell lines + if (Style.ShowVerticalCellLines) { + foreach (var col in columnsToRender) { + var lineStyle = Style.InnerLineStyle; + if (col.X - 1 == 0) { + lineStyle = Style.OuterLineStyle; + } + grid.AddLine (new Point (col.X - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, lineStyle); } + grid.AddLine (new Point (width - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, Style.OuterLineStyle); } - //render end of line - if (style.ShowVerticalHeaderLines) - AddRune (Bounds.Width - 1, row, Driver.VLine); + // render the bottom line + if (Style.ShowHorizontalBottomline) { + grid.AddLine (new Point (0, height - RowOffset + hh - 1), width, Orientation.Horizontal, Style.OuterLineStyle); + } } - private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender [] columnsToRender) + private bool MoreColumnsToLeft () { - /* - * First lets work out if we should be rendering scroll indicators - */ - // are there are visible columns to the left that have been pushed // off the screen due to horizontal scrolling? bool moreColumnsToLeft = ColumnOffset > 0; // if we moved left would we find a new column (or are they all invisible?) if (!TryGetNearestVisibleColumn (ColumnOffset - 1, false, false, out _)) { - moreColumnsToLeft = false; + return false; } + return moreColumnsToLeft; + } + + private bool MoreColumnsToRight (ColumnToRender [] columnsToRender) + { // are there visible columns to the right that have not yet been reached? // lets find out, what is the column index of the last column we are rendering int lastColumnIdxRendered = ColumnOffset + columnsToRender.Length - 1; @@ -425,104 +478,12 @@ private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender // if we went right from the last column would we find a new visible column? if (!TryGetNearestVisibleColumn (lastColumnIdxRendered + 1, true, false, out _)) { // no we would not - moreColumnsToRight = false; - } - - /* - * Now lets draw the line itself - */ - - // Renders a line below the table headers (when visible) like: - // ├──────────┼───────────┼───────────────────┼──────────┼────────┼─────────────┤ - - for (int c = 0; c < availableWidth; c++) { - - // Start by assuming we just draw a straight line the - // whole way but update to instead draw a header indicator - // or scroll arrow etc - var rune = Driver.HLine; - - if (Style.ShowVerticalHeaderLines) { - if (c == 0) { - // for first character render line - rune = Style.ShowVerticalCellLines ? Driver.LeftTee : Driver.LLCorner; - - // unless we have horizontally scrolled along - // in which case render an arrow, to indicate user - // can scroll left - if (Style.ShowHorizontalScrollIndicators && moreColumnsToLeft) { - rune = Driver.LeftArrow; - scrollLeftPoint = new Point (c, row); - } - - } - // if the next column is the start of a header - else if (columnsToRender.Any (r => r.X == c + 1)) { - - /*TODO: is ┼ symbol in Driver?*/ - rune = Style.ShowVerticalCellLines ? '┼' : Driver.BottomTee; - } else if (c == availableWidth - 1) { - - // for the last character in the table - rune = Style.ShowVerticalCellLines ? Driver.RightTee : Driver.LRCorner; - - // unless there is more of the table we could horizontally - // scroll along to see. In which case render an arrow, - // to indicate user can scroll right - if (Style.ShowHorizontalScrollIndicators && moreColumnsToRight) { - rune = Driver.RightArrow; - scrollRightPoint = new Point (c, row); - } - - } - // if the next console column is the lastcolumns end - else if (Style.ExpandLastColumn == false && - columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { - rune = Style.ShowVerticalCellLines ? '┼' : Driver.BottomTee; - } - } - - AddRuneAt (Driver, c, row, rune); + return false; } + return moreColumnsToRight; } - private void RenderBottomLine (int row, int availableWidth, ColumnToRender [] columnsToRender) - { - // Renders a line at the bottom of the table after all the data like: - // └─────────────────────────────────┴──────────┴──────┴──────────┴────────┴────────────────────────────────────────────┘ - - for (int c = 0; c < availableWidth; c++) { - - // Start by assuming we just draw a straight line the - // whole way but update to instead draw BottomTee / Corner etc - var rune = Driver.HLine; - - if (Style.ShowVerticalCellLines) { - if (c == 0) { - // for first character render line - rune = Driver.LLCorner; - - } - // if the next column is the start of a header - else if (columnsToRender.Any (r => r.X == c + 1)) { - rune = Driver.BottomTee; - } else if (c == availableWidth - 1) { - - // for the last character in the table - rune = Driver.LRCorner; - - } - // if the next console column is the lastcolumns end - else if (Style.ExpandLastColumn == false && - columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { - rune = Driver.BottomTee; - } - } - - AddRuneAt (Driver, c, row, rune); - } - } private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRender) { var focused = HasFocus; @@ -541,8 +502,8 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen color = Enabled ? rowScheme.Normal : rowScheme.Disabled; } - Driver.SetAttribute (color); - Driver.AddStr (new string (' ', Bounds.Width)); + //Driver.SetAttribute (color); + //Driver.AddStr (new string (' ', Bounds.Width)); // Render cells for each visible header for the current row for (int i = 0; i < columnsToRender.Length; i++) { @@ -605,31 +566,19 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen Driver.SetAttribute (color); } - // If not in full row select mode always, reset color scheme to normal and render the vertical line (or space) at the end of the cell - if (!FullRowSelect) - Driver.SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled); - - if(style.AlwaysUseNormalColorForVerticalCellLines && style.ShowVerticalCellLines) { - - Driver.SetAttribute (rowScheme.Normal); - } - - RenderSeparator (current.X - 1, row, false); - - if (Style.ExpandLastColumn == false && current.IsVeryLast) { - RenderSeparator (current.X + current.Width - 1, row, false); + // TODO: style.AlwaysUseNormalColorForVerticalCellLines is no longer possible after switch to LineCanvas + // This is an attempt at a workaround, but requires cell lines to be drawn first and doesn't respect LineStyle + /* + if (FullRowSelect && style.ShowVerticalCellLines && !style.AlwaysUseNormalColorForVerticalCellLines) { + if (current.X - 1 != 0) { + RenderSeparator (current.X - 1, row, false); + } + if (Style.ExpandLastColumn == false && current.IsVeryLast) { + RenderSeparator (current.X + current.Width - 1, row, false); + } } + */ } - - if (style.ShowVerticalCellLines) { - - Driver.SetAttribute (rowScheme.Normal); - - //render start and end of line - AddRune (0, row, Driver.VLine); - AddRune (Bounds.Width - 1, row, Driver.VLine); - } - } /// @@ -689,11 +638,12 @@ void AddRuneAt (ConsoleDriver d, int col, int row, Rune ch) /// The string representation of /// /// Optional style indicating custom alignment for the cell + /// Character used to pad string (defaults to space) /// - private string TruncateOrPad (object originalCellValue, string representation, int availableHorizontalSpace, ColumnStyle colStyle) + private string TruncateOrPad (object originalCellValue, string representation, int availableHorizontalSpace, ColumnStyle colStyle, char padChar = ' ') { if (string.IsNullOrEmpty (representation)) - return new string(' ',availableHorizontalSpace); + return new string (padChar, availableHorizontalSpace); // if value is not wide enough if (representation.Sum (c => Rune.ColumnWidth (c)) < availableHorizontalSpace) { @@ -704,17 +654,17 @@ private string TruncateOrPad (object originalCellValue, string representation, i switch (colStyle?.GetAlignment (originalCellValue) ?? TextAlignment.Left) { case TextAlignment.Left: - return representation + new string (' ', toPad); + return representation + new string (padChar, toPad); case TextAlignment.Right: - return new string (' ', toPad) + representation; + return new string (padChar, toPad) + representation; // TODO: With single line cells, centered and justified are the same right? case TextAlignment.Centered: case TextAlignment.Justified: return - new string (' ', (int)Math.Floor (toPad / 2.0)) + // round down + new string (padChar, (int)Math.Floor (toPad / 2.0)) + // round down representation + - new string (' ', (int)Math.Ceiling (toPad / 2.0)); // round up + new string (padChar, (int)Math.Ceiling (toPad / 2.0)); // round up } } @@ -1833,6 +1783,11 @@ public class TableStyle { /// public bool ShowHorizontalHeaderUnderline { get; set; } = true; + /// + /// True to render a solid line through the headers (only when Overline and/or Underline are ) + /// + public bool ShowHorizontalHeaderThroughline { get; set; } = true; + /// /// True to render a solid line vertical line between cells /// @@ -1845,9 +1800,9 @@ public class TableStyle { /// /// True to render a arrows on the right/left of the table when - /// there are more column(s) that can be scrolled to. Requires - /// to be true. - /// Defaults to true + /// there are more column(s) that can be scrolled to. Applied to the + /// lowest Horizontal Header line. + /// Defaults to . /// public bool ShowHorizontalScrollIndicators { get; set; } = true; @@ -1865,17 +1820,43 @@ public class TableStyle { /// public bool InvertSelectedCellFirstCharacter { get; set; } = false; + // TODO: I changed the default to true for now, but false doesn't work currently /// /// Gets or sets a flag indicating whether to force use when rendering /// vertical cell lines (even when is on). /// - public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false; + public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = true; /// /// Collection of columns for which you want special rendering (e.g. custom column lengths, text alignment etc) /// public Dictionary ColumnStyles { get; set; } = new Dictionary (); + /// + /// The colors to use for border lines) + /// + public Attribute LineColor { get; set; } + + /// + /// The style to use for border lines within the header) + /// + public LineStyle OuterHeaderLineStyle { get; set; } = LineStyle.Single; + + /// + /// The style to use for border lines within the header) + /// + public LineStyle InnerHeaderLineStyle { get; set; } = LineStyle.Single; + + /// + /// The style to use for border lines around the edge of the table) + /// + public LineStyle OuterLineStyle { get; set; } = LineStyle.Single; + + /// + /// The style to use for border lines within the table) + /// + public LineStyle InnerLineStyle { get; set; } = LineStyle.Single; + /// /// Delegate for coloring specific rows in a different color. For cell color /// diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index ab67ee2914..62128464d0 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -20,6 +20,7 @@ public class TableEditor : Scenario { private MenuItem miAlwaysShowHeaders; private MenuItem miHeaderOverline; private MenuItem miHeaderMidline; + private MenuItem miHeaderThruline; private MenuItem miHeaderUnderline; private MenuItem miShowHorizontalScrollIndicators; private MenuItem miCellLines; @@ -61,7 +62,8 @@ public override void Setup () miShowHeaders = new MenuItem ("_ShowHeaders", "", () => ToggleShowHeaders()){Checked = tableView.Style.ShowHeaders, CheckType = MenuItemCheckStyle.Checked }, miAlwaysShowHeaders = new MenuItem ("_AlwaysShowHeaders", "", () => ToggleAlwaysShowHeaders()){Checked = tableView.Style.AlwaysShowHeaders, CheckType = MenuItemCheckStyle.Checked }, miHeaderOverline = new MenuItem ("_HeaderOverLine", "", () => ToggleOverline()){Checked = tableView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked }, - miHeaderMidline = new MenuItem ("_HeaderMidLine", "", () => ToggleHeaderMidline()){Checked = tableView.Style.ShowVerticalHeaderLines, CheckType = MenuItemCheckStyle.Checked }, + miHeaderMidline = new MenuItem ("_VerticalHeaderLines", "", () => ToggleHeaderMidline()){Checked = tableView.Style.ShowVerticalHeaderLines, CheckType = MenuItemCheckStyle.Checked }, + miHeaderThruline = new MenuItem ("_HeaderThroughLine", "", () => ToggleHeaderThruline()){Checked = tableView.Style.ShowHorizontalHeaderThroughline, CheckType = MenuItemCheckStyle.Checked }, miHeaderUnderline = new MenuItem ("_HeaderUnderLine", "", () => ToggleUnderline()){Checked = tableView.Style.ShowHorizontalHeaderUnderline, CheckType = MenuItemCheckStyle.Checked }, miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline()){Checked = tableView.Style.ShowHorizontalBottomline, CheckType = MenuItemCheckStyle.Checked }, miShowHorizontalScrollIndicators = new MenuItem ("_HorizontalScrollIndicators", "", () => ToggleHorizontalScrollIndicators()){Checked = tableView.Style.ShowHorizontalScrollIndicators, CheckType = MenuItemCheckStyle.Checked }, @@ -404,6 +406,12 @@ private void ToggleHeaderMidline () tableView.Style.ShowVerticalHeaderLines = (bool)miHeaderMidline.Checked; tableView.Update (); } + private void ToggleHeaderThruline () + { + miHeaderThruline.Checked = !miHeaderThruline.Checked; + tableView.Style.ShowHorizontalHeaderThroughline = (bool)miHeaderThruline.Checked; + tableView.Update (); + } private void ToggleUnderline () { miHeaderUnderline.Checked = !miHeaderUnderline.Checked; @@ -468,6 +476,7 @@ private void ToggleAllCellLines () miHeaderOverline.Checked = true; miHeaderMidline.Checked = true; + miHeaderThruline.Checked = true; miHeaderUnderline.Checked = true; miCellLines.Checked = true; @@ -482,6 +491,7 @@ private void ToggleNoCellLines () miHeaderOverline.Checked = false; miHeaderMidline.Checked = false; + miHeaderThruline.Checked = true; miHeaderUnderline.Checked = false; miCellLines.Checked = false; From cab5ca990a6333a7eeea231cd3f9e1b2a7aada23 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Tue, 2 May 2023 18:12:06 -0700 Subject: [PATCH 02/18] Fix merge --- Terminal.Gui/Views/TableView/TableView.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 8a0b78720e..ef802c7db4 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -249,6 +249,7 @@ public override void Redraw (Rect bounds) base.Redraw (bounds); Move (0, 0); + var frame = Frame; // What columns to render at what X offset in viewport var columnsToRender = CalculateViewport (bounds).ToArray (); @@ -274,7 +275,7 @@ public override void Redraw (Rect bounds) Driver.SetAttribute (Style.LineColor); else Driver.SetAttribute (this.ColorScheme.Normal); - RenderCellLines (width, Table.Rows.Count, columnsToRender); + RenderCellLines (width, Table.Rows, columnsToRender); foreach (var p in grid.GetMap (bounds)) { this.AddRune (p.Key.X, p.Key.Y, p.Value); @@ -317,7 +318,7 @@ public override void Redraw (Rect bounds) var current = columnsToRender [i]; var colStyle = Style.GetColumnStyleIfAny (current.Column); - var colName = current.Column.ColumnName; + var colName = Table.ColumnNames [i]; //RenderSeparator (current.X - 1, yh, true); From 385f6688cb85c9a7c685e5b16394d5cbc8587bff Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Tue, 2 May 2023 18:53:24 -0700 Subject: [PATCH 03/18] Fix column names --- Terminal.Gui/Views/TableView/TableView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index ef802c7db4..57cdacffaf 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -318,7 +318,7 @@ public override void Redraw (Rect bounds) var current = columnsToRender [i]; var colStyle = Style.GetColumnStyleIfAny (current.Column); - var colName = Table.ColumnNames [i]; + var colName = table.ColumnNames [current.Column]; //RenderSeparator (current.X - 1, yh, true); From 95a13cfe8d7d6ffc3ed091d8025bdf800ed7f3a9 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Tue, 2 May 2023 18:59:28 -0700 Subject: [PATCH 04/18] Fix most of the failing table unit tests --- UnitTests/Views/TableViewTests.cs | 36 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index fd4907d192..e359dab519 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -452,6 +452,7 @@ public void TableView_ShowHeadersFalse_AndNoHeaderLines () tv.Style.ShowHeaders = false; tv.Style.ShowHorizontalHeaderOverline = false; + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.ShowHorizontalHeaderUnderline = false; tv.Redraw (tv.Bounds); @@ -469,12 +470,13 @@ public void TableView_ShowHeadersFalse_OverlineTrue () tv.Style.ShowHeaders = false; tv.Style.ShowHorizontalHeaderOverline = true; + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.ShowHorizontalHeaderUnderline = false; tv.Redraw (tv.Bounds); string expected = @" -┌─┬─┐ +┌─┬─► │1│2│ "; TestHelpers.AssertDriverContentsAre (expected, output); @@ -487,15 +489,16 @@ public void TableView_ShowHeadersFalse_UnderlineTrue () tv.Style.ShowHeaders = false; tv.Style.ShowHorizontalHeaderOverline = false; + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.ShowHorizontalHeaderUnderline = true; - // Horizontal scrolling option is part of the underline + // Horizontal scrolling option is the lowest header line tv.Style.ShowHorizontalScrollIndicators = true; tv.Redraw (tv.Bounds); string expected = @" -├─┼─► +┌─┬─► │1│2│ "; TestHelpers.AssertDriverContentsAre (expected, output); @@ -509,8 +512,9 @@ public void TableView_ShowHeadersFalse_AllLines () tv.Style.ShowHeaders = false; tv.Style.ShowHorizontalHeaderOverline = true; + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.ShowHorizontalHeaderUnderline = true; - // Horizontal scrolling option is part of the underline + // Horizontal scrolling option is the lowest header line tv.Style.ShowHorizontalScrollIndicators = true; @@ -1130,6 +1134,7 @@ public void ScrollRight_SmoothScrolling () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = false; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -1153,7 +1158,7 @@ public void ScrollRight_SmoothScrolling () string expected = @" -│A│B│C│ +│A│B│C► │1│2│3│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1172,7 +1177,7 @@ public void ScrollRight_SmoothScrolling () expected = @" -│B│C│D│ +◄B│C│D► │2│3│4│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1191,6 +1196,7 @@ public void ScrollRight_WithoutSmoothScrolling () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = false; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = false; @@ -1214,7 +1220,7 @@ public void ScrollRight_WithoutSmoothScrolling () string expected = @" -│A│B│C│ +│A│B│C► │1│2│3│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1232,7 +1238,7 @@ public void ScrollRight_WithoutSmoothScrolling () expected = @" -│D│E│F│ +◄D│E│F│ │4│5│6│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1251,6 +1257,7 @@ private TableView GetABCDEFTableView (out DataTable dt) // 3 columns are visible tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = false; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = false; @@ -1280,7 +1287,7 @@ public void TestColumnStyle_VisibleFalse_IsNotRendered () string expected = @" -│A│C│D│ +│A│C│D► │1│3│4│"; TestHelpers.AssertDriverContentsAre (expected, output); @@ -1802,6 +1809,7 @@ public void LongColumnTest () // 25 characters can be printed into table tableView.Bounds = new Rect (0, 0, 25, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -1943,6 +1951,7 @@ public void ScrollIndicators () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -2086,6 +2095,7 @@ public void ShowHorizontalBottomLine_WithVerticalCellLines () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = true; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -2097,7 +2107,7 @@ public void ShowHorizontalBottomLine_WithVerticalCellLines () // Because first column in table is A string expected = @" -│A│B│C│ +┌A┬B┬C┐ ├─┼─┼─► │1│2│3│ └─┴─┴─┘"; @@ -2115,6 +2125,7 @@ public void ShowHorizontalBottomLine_NoCellLines () // 3 columns are visibile tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = true; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; @@ -2127,7 +2138,7 @@ public void ShowHorizontalBottomLine_NoCellLines () // Because first column in table is A string expected = @" -│A│B│C│ +┌A┬B┬C┐ └─┴─┴─► 1 2 3 ───────"; @@ -2279,7 +2290,7 @@ public void TestFullRowSelect_SelectionColorDoesNotStop_WhenShowVerticalCellLine string expected = @" A B C -─────── +──────► 1 2 3 1 2 3 1 2 3"; @@ -2617,6 +2628,7 @@ private TableView GetTwoRowSixColumnTable (out DataTable dt) // 3 columns are visible tableView.Bounds = new Rect (0, 0, 7, 5); tableView.Style.ShowHorizontalHeaderUnderline = true; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.AlwaysShowHeaders = true; tableView.Style.SmoothHorizontalScrolling = true; From 4f5bab78fb3d4fb86deeba5d1daa5c1defcfc783 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Thu, 4 May 2023 12:42:48 -0700 Subject: [PATCH 05/18] Fix merge --- Terminal.Gui/Views/TableView/TableView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 295d64ce78..d399c14c3c 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -264,7 +264,7 @@ public override void OnDrawContent (Rect contentArea) } var lastCol = columnsToRender [columnsToRender.Length - 1]; - var width = bounds.Width; + var width = Bounds.Width; if (!Style.ExpandLastColumn) { width = lastCol.X + lastCol.Width; } @@ -277,7 +277,7 @@ public override void OnDrawContent (Rect contentArea) Driver.SetAttribute (this.ColorScheme.Normal); RenderCellLines (width, Table.Rows, columnsToRender); - foreach (var p in grid.GetMap (bounds)) { + foreach (var p in grid.GetMap (Bounds)) { this.AddRune (p.Key.X, p.Key.Y, p.Value); } From f03609117c3c180a93faf2e22435a0d105cd1f76 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Sat, 6 May 2023 12:31:20 -0700 Subject: [PATCH 06/18] Fix missing separator after null cell entries where NullSymbol is null or empty string Also: * Add settable header/cell symbols to pad around values * Fix All/NoLines in TableEditor scenario to apply to HeaderThroughline --- Terminal.Gui/Views/TableView/TableView.cs | 22 +++++++++++++++++----- UICatalog/Scenarios/TableEditor.cs | 4 +++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index d399c14c3c..f46aa05f47 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -137,6 +137,16 @@ public int SelectedRow { /// public char SeparatorSymbol { get; set; } = ' '; + /// + /// The symbol to pad around values (between separators) + /// + public char HeaderPaddingSymbol { get; set; } = ' '; + + /// + /// The symbol to pad around values (between separators) + /// + public char CellPaddingSymbol { get; set; } = ' '; + /// /// This event is raised when the selected cell in the table changes. /// @@ -302,7 +312,7 @@ public override void OnDrawContent (Rect contentArea) // render the header contents if (Style.ShowHeaders && hh > 0) { - var padChar = ' '; + var padChar = HeaderPaddingSymbol; if (Style.ShowHorizontalHeaderThroughline && (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { padChar = (char)Driver.HLine; @@ -337,6 +347,8 @@ public override void OnDrawContent (Rect contentArea) // render the cell contents for (var line = hh; line < frame.Height; line++) { + var padChar = CellPaddingSymbol; + //ClearLine (line, width); //work out what Row to render @@ -356,7 +368,7 @@ public override void OnDrawContent (Rect contentArea) continue; } - RenderRow (line, rowToRender, columnsToRender); + RenderRow (line, rowToRender, columnsToRender, padChar); } } @@ -499,7 +511,7 @@ private bool MoreColumnsToRight (ColumnToRender [] columnsToRender) return moreColumnsToRight; } - private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRender) + private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRender, char padChar) { var focused = HasFocus; @@ -563,7 +575,7 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen cellColor = Enabled ? scheme.Normal : scheme.Disabled; } - var render = TruncateOrPad (val, representation, current.Width, colStyle); + var render = TruncateOrPad (val, representation, current.Width, colStyle, padChar); // While many cells can be selected (see MultiSelectedRegions) only one cell is the primary (drives navigation etc) bool isPrimaryCell = current.Column == selectedColumn && rowToRender == selectedRow; @@ -1680,7 +1692,7 @@ private int CalculateMaxCellWidth (int col, int rowsToRender, ColumnStyle colSty private string GetRepresentation (object value, ColumnStyle colStyle) { if (value == null || value == DBNull.Value) { - return NullSymbol; + return string.IsNullOrEmpty(NullSymbol) ? " " : NullSymbol; } return colStyle != null ? colStyle.GetRepresentation (value) : value.ToString (); diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index 1eb4689de2..be60dbe476 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -474,6 +474,7 @@ private void ToggleAllCellLines () { tableView.Style.ShowHorizontalHeaderOverline = true; tableView.Style.ShowVerticalHeaderLines = true; + tableView.Style.ShowHorizontalHeaderThroughline = true; tableView.Style.ShowHorizontalHeaderUnderline = true; tableView.Style.ShowVerticalCellLines = true; @@ -489,12 +490,13 @@ private void ToggleNoCellLines () { tableView.Style.ShowHorizontalHeaderOverline = false; tableView.Style.ShowVerticalHeaderLines = false; + tableView.Style.ShowHorizontalHeaderThroughline = false; tableView.Style.ShowHorizontalHeaderUnderline = false; tableView.Style.ShowVerticalCellLines = false; miHeaderOverline.Checked = false; miHeaderMidline.Checked = false; - miHeaderThruline.Checked = true; + miHeaderThruline.Checked = false; miHeaderUnderline.Checked = false; miCellLines.Checked = false; From 8313dc046c9b98f318f12d777444adef37f1d980 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Sat, 6 May 2023 13:16:04 -0700 Subject: [PATCH 07/18] Fix SeparatorSymbol (when cell lines are disabled) Style.AlwaysUseNormalColorForVerticalCellLines is obeyed now only if cell lines are disabled --- Terminal.Gui/Views/TableView/TableView.cs | 56 ++++++++++------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index f46aa05f47..d29dce29b3 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -330,17 +330,17 @@ public override void OnDrawContent (Rect contentArea) var colStyle = Style.GetColumnStyleIfAny (current.Column); var colName = table.ColumnNames [current.Column]; - //RenderSeparator (current.X - 1, yh, true); + if (!Style.ShowVerticalHeaderLines && current.X - 1 >= 0) { + AddRune (current.X - 1, yh, SeparatorSymbol); + } Move (current.X, yh); Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle, padChar)); - /* - if (Style.ExpandLastColumn == false && current.IsVeryLast) { - RenderSeparator (current.X + current.Width - 1, yh, true); + if (!Style.ShowVerticalHeaderLines && !Style.ExpandLastColumn && current.IsVeryLast) { + AddRune (current.X + current.Width - 1, yh, SeparatorSymbol); } - */ } } @@ -582,29 +582,32 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen RenderCell (cellColor, render, isPrimaryCell); - // Reset color scheme to normal for drawing separators if we drew text with custom scheme - if (scheme != rowScheme) { + // Style.AlwaysUseNormalColorForVerticalCellLines is no longer possible after switch to LineCanvas + // except when cell lines are disabled and SeparatorSymbol is used - if (isSelectedCell) { - color = focused ? rowScheme.HotFocus : rowScheme.HotNormal; - } else { - color = Enabled ? rowScheme.Normal : rowScheme.Disabled; + if (!Style.ShowVerticalCellLines) { + + if (Style.AlwaysUseNormalColorForVerticalCellLines) { + color = rowScheme.Normal; + + } else if (scheme != rowScheme) { + + // Reset color scheme to normal for drawing separators if we drew text with custom scheme + if (isSelectedCell) { + color = focused ? rowScheme.HotFocus : rowScheme.HotNormal; + } else { + color = Enabled ? rowScheme.Normal : rowScheme.Disabled; + } } Driver.SetAttribute (color); - } - // TODO: style.AlwaysUseNormalColorForVerticalCellLines is no longer possible after switch to LineCanvas - // This is an attempt at a workaround, but requires cell lines to be drawn first and doesn't respect LineStyle - /* - if (FullRowSelect && style.ShowVerticalCellLines && !style.AlwaysUseNormalColorForVerticalCellLines) { - if (current.X - 1 != 0) { - RenderSeparator (current.X - 1, row, false); + if (current.X - 1 >= 0) { + AddRune (current.X - 1, row, SeparatorSymbol); } - if (Style.ExpandLastColumn == false && current.IsVeryLast) { - RenderSeparator (current.X + current.Width - 1, row, false); + if (!Style.ExpandLastColumn && current.IsVeryLast) { + AddRune (current.X + current.Width - 1, row, SeparatorSymbol); } } - */ } } @@ -641,17 +644,6 @@ protected virtual void RenderCell (Attribute cellColor, string render, bool isPr } } - private void RenderSeparator (int col, int row, bool isHeader) - { - if (col < 0) - return; - - var renderLines = isHeader ? style.ShowVerticalHeaderLines : style.ShowVerticalCellLines; - - Rune symbol = renderLines ? Driver.VLine : SeparatorSymbol; - AddRune (col, row, symbol); - } - void AddRuneAt (ConsoleDriver d, int col, int row, Rune ch) { Move (col, row); From f11a5d1780bfb8027568f18812be1e5433e9d6b5 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Sat, 6 May 2023 13:31:38 -0700 Subject: [PATCH 08/18] Change default for AlwaysUseNormalColorForVerticalCellLines back to false. --- Terminal.Gui/Views/TableView/TableView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index d29dce29b3..5b4b9bbfe8 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -1871,12 +1871,12 @@ public class TableStyle { /// public bool InvertSelectedCellFirstCharacter { get; set; } = false; - // TODO: I changed the default to true for now, but false doesn't work currently + // TODO: Fix this for when cell lines are enabled. /// /// Gets or sets a flag indicating whether to force use when rendering /// vertical cell lines (even when is on). /// - public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = true; + public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false; /// /// Collection of columns for which you want special rendering (e.g. custom column lengths, text alignment etc) From 9f239741819a56ae5a68a0e0d718ea78f291beb7 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Tue, 9 May 2023 17:29:09 -0700 Subject: [PATCH 09/18] Fix merge --- Terminal.Gui/Views/TableView/TableView.cs | 6 +++--- UICatalog/Scenarios/TableEditor.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index fa4ac164cc..97f11a453a 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -304,10 +304,10 @@ public override void OnDrawContent (Rect contentArea) } } if (scrollLeftPoint != null) { - AddRuneAt (Driver, 0, scrollLeftPoint.Value.Y - 1, Driver.LeftArrow); + AddRuneAt (Driver, 0, scrollLeftPoint.Value.Y - 1, CM.Glyphs.LeftArrow); } if (scrollRightPoint != null) { - AddRuneAt (Driver, scrollRightPoint.Value.X, scrollRightPoint.Value.Y - 1, Driver.RightArrow); + AddRuneAt (Driver, scrollRightPoint.Value.X, scrollRightPoint.Value.Y - 1, CM.Glyphs.RightArrow); } // render the header contents @@ -315,7 +315,7 @@ public override void OnDrawContent (Rect contentArea) var padChar = HeaderPaddingSymbol; if (Style.ShowHorizontalHeaderThroughline && (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { - padChar = (char)Driver.HLine; + padChar = (char)CM.Glyphs.HLine; } var yh = hh - 1; diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index 8f69d2ad25..ccea47d017 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -405,8 +405,8 @@ private void ToggleOverline () } private void ToggleThruline () { - miHeaderThruline.Checked = !miHeaderThruline.Checked; - tableView.Style.ShowHorizontalHeaderThroughline = (bool)miHeaderThruline.Checked; + _miHeaderThruline.Checked = !_miHeaderThruline.Checked; + tableView.Style.ShowHorizontalHeaderThroughline = (bool)_miHeaderThruline.Checked; tableView.Update (); } private void ToggleUnderline () From 3a1d3ee826aa05cd12679df04dfb1647e7f95221 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Wed, 10 May 2023 09:39:14 -0700 Subject: [PATCH 10/18] Add table LineStyles to TableEditor scenario --- UICatalog/Scenarios/TableEditor.cs | 138 ++++++++++++++++++++++++----- 1 file changed, 118 insertions(+), 20 deletions(-) diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index ccea47d017..6657d46811 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -37,6 +37,14 @@ public class TableEditor : Scenario { ColorScheme redColorSchemeAlt; ColorScheme alternatingColorScheme; + enum TableLineStyleType + { + OuterHeaderLineStyle, + InnerHeaderLineStyle, + OuterLineStyle, + InnerLineStyle, + } + public override void Setup () { Win.Title = this.GetName (); @@ -53,38 +61,39 @@ public override void Setup () var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("_OpenBigExample", "", () => OpenExample(true)), - new MenuItem ("_OpenSmallExample", "", () => OpenExample(false)), + new MenuItem ("Open_BigExample", "", () => OpenExample(true)), + new MenuItem ("Open_SmallExample", "", () => OpenExample(false)), new MenuItem ("OpenCharacter_Map","",()=>OpenUnicodeMap()), new MenuItem ("_CloseExample", "", () => CloseExample()), new MenuItem ("_Quit", "", () => Quit()), }), new MenuBarItem ("_View", new MenuItem [] { - _miShowHeaders = new MenuItem ("_ShowHeaders", "", () => ToggleShowHeaders()){Checked = tableView.Style.ShowHeaders, CheckType = MenuItemCheckStyle.Checked }, + _miShowHeaders = new MenuItem ("Show_Headers", "", () => ToggleShowHeaders()){Checked = tableView.Style.ShowHeaders, CheckType = MenuItemCheckStyle.Checked }, _miAlwaysShowHeaders = new MenuItem ("_AlwaysShowHeaders", "", () => ToggleAlwaysShowHeaders()){Checked = tableView.Style.AlwaysShowHeaders, CheckType = MenuItemCheckStyle.Checked }, - _miHeaderOverline = new MenuItem ("_HeaderOverLine", "", () => ToggleOverline()){Checked = tableView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked }, - _miHeaderThruline = new MenuItem ("_HeaderThruLine", "", () => ToggleThruline()){Checked = tableView.Style.ShowHorizontalHeaderThroughline, CheckType = MenuItemCheckStyle.Checked }, - _miHeaderUnderline = new MenuItem ("_HeaderUnderLine", "", () => ToggleUnderline()){Checked = tableView.Style.ShowHorizontalHeaderUnderline, CheckType = MenuItemCheckStyle.Checked }, - _miHeaderVertline = new MenuItem ("_HeaderVertLine", "", () => ToggleHeaderVertline()){Checked = tableView.Style.ShowVerticalHeaderLines, CheckType = MenuItemCheckStyle.Checked }, + _miHeaderOverline = new MenuItem ("Header_OverLine", "", () => ToggleOverline()){Checked = tableView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked }, + _miHeaderThruline = new MenuItem ("Header_ThroughLine", "", () => ToggleThruline()){Checked = tableView.Style.ShowHorizontalHeaderThroughline, CheckType = MenuItemCheckStyle.Checked }, + _miHeaderUnderline = new MenuItem ("Header_UnderLine", "", () => ToggleUnderline()){Checked = tableView.Style.ShowHorizontalHeaderUnderline, CheckType = MenuItemCheckStyle.Checked }, + _miHeaderVertline = new MenuItem ("_VerticalHeaderLines", "", () => ToggleHeaderVertline()){Checked = tableView.Style.ShowVerticalHeaderLines, CheckType = MenuItemCheckStyle.Checked }, _miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline()){Checked = tableView.Style.ShowHorizontalBottomline, CheckType = MenuItemCheckStyle.Checked }, - _miShowHorizontalScrollIndicators = new MenuItem ("_HorizontalScrollIndicators", "", () => ToggleHorizontalScrollIndicators()){Checked = tableView.Style.ShowHorizontalScrollIndicators, CheckType = MenuItemCheckStyle.Checked }, + _miShowHorizontalScrollIndicators = new MenuItem ("Horizontal_ScrollIndicators", "", () => ToggleHorizontalScrollIndicators()){Checked = tableView.Style.ShowHorizontalScrollIndicators, CheckType = MenuItemCheckStyle.Checked }, _miFullRowSelect =new MenuItem ("_FullRowSelect", "", () => ToggleFullRowSelect()){Checked = tableView.FullRowSelect, CheckType = MenuItemCheckStyle.Checked }, _miCellLines =new MenuItem ("_CellLines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + _miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("AlwaysUse_NormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines()){Checked = tableView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + new MenuItem ("_LineStyles", "", () => SetLineStyles()), + new MenuItem ("AllLines", "", () => ToggleAllCellLines()), + new MenuItem ("NoLines", "", () => ToggleNoCellLines()), _miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn()){Checked = tableView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked }, - _miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines()){Checked = tableView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, - _miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling()){Checked = tableView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked }, - new MenuItem ("_AllLines", "", () => ToggleAllCellLines()), - new MenuItem ("_NoLines", "", () => ToggleNoCellLines()), - _miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked}, - _miCursor = new MenuItem ("Invert Selected Cell First Character", "", () => ToggleInvertSelectedCellFirstCharacter()){Checked = tableView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked}, - new MenuItem ("_ClearColumnStyles", "", () => ClearColumnStyles()), - new MenuItem ("Sho_w All Columns", "", ()=>ShowAllColumns()) + _miSmoothScrolling = new MenuItem ("S_moothHorizontalScrolling", "", () => ToggleSmoothScrolling()){Checked = tableView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked }, + _miAlternatingColors = new MenuItem ("Alte_rnatingColors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked}, + _miCursor = new MenuItem ("_InvertSelectedCellFirstCharacter", "", () => ToggleInvertSelectedCellFirstCharacter()){Checked = tableView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked}, + new MenuItem ("ClearColumnSt_yles", "", () => ClearColumnStyles()), + new MenuItem ("Sho_wAllColumns", "", ()=>ShowAllColumns()) }), new MenuBarItem ("_Column", new MenuItem [] { - new MenuItem ("_Set Max Width", "", SetMaxWidth), - new MenuItem ("_Set Min Width", "", SetMinWidth), - new MenuItem ("_Set MinAcceptableWidth", "",SetMinAcceptableWidth), - new MenuItem ("_Set All MinAcceptableWidth=1", "",SetMinAcceptableWidthToOne), + new MenuItem ("Set Ma_x Width", "", SetMaxWidth), + new MenuItem ("Set Mi_n Width", "", SetMinWidth), + new MenuItem ("Set MinAcceptable_Width", "",SetMinAcceptableWidth), + new MenuItem ("Set _All MinAcceptableWidth=1", "",SetMinAcceptableWidthToOne), }), }); @@ -325,6 +334,95 @@ private void RunColumnWidthDialog (DataColumn col, string prompt, Action NStack.ustring.Make (e.ToString ())).ToArray ()) { + X = 0, + Y = 0, + }; + + var accepted = false; + var ok = new Button ("Ok", is_default: true); + ok.Clicked += (s, e) => { accepted = true; Application.RequestStop (); }; + var cancel = new Button ("Cancel"); + cancel.Clicked += (s, e) => { Application.RequestStop (); }; + var d = new Dialog (ok, cancel) { Title = "BorderStyles" }; + + var borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast ().ToList (); + var rbBorderStyle = new RadioGroup (borderStyleEnum.Select ( + e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + X = Pos.Right (rbBorderType) + 2, + Y = 0, + SelectedItem = (int) chosenStyle + }; + + // Change selection to existing style value for given type + rbBorderType.SelectedItemChanged += (s, e) => { + chosenType = (TableLineStyleType) e.SelectedItem; + var str = chosenType.ToString (); + switch (e.SelectedItem) { + case 0: + rbBorderStyle.SelectedItem = (int) (TableLineStyleType) style.OuterHeaderLineStyle; + break; + case 1: + rbBorderStyle.SelectedItem = (int) (TableLineStyleType) style.InnerHeaderLineStyle; + break; + case 2: + rbBorderStyle.SelectedItem = (int) (TableLineStyleType) style.OuterLineStyle; + break; + case 3: + rbBorderStyle.SelectedItem = (int) (TableLineStyleType) style.InnerLineStyle; + break; + } + }; + rbBorderStyle.SelectedItemChanged += (s, e) => { + chosenStyle = (LineStyle) e.SelectedItem; + }; + + d.Add (rbBorderType, rbBorderStyle); + + Application.Run (d); + + if (accepted) { + + try { + switch ((int)chosenType) { + case 0: + Action setterHdrOut = (s, v) => s.OuterHeaderLineStyle = v; + setterHdrOut (style, chosenStyle); + break; + case 1: + Action setterHdrIn = (s, v) => s.InnerHeaderLineStyle = v; + setterHdrIn (style, chosenStyle); + break; + case 2: + Action setterOut = (s, v) => s.OuterLineStyle = v; + setterOut (style, chosenStyle); + break; + case 3: + Action setterIn = (s, v) => s.InnerLineStyle = v; + setterIn (style, chosenStyle); + break; + } + } catch (Exception ex) { + MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok"); + } + + tableView.Update (); + } + } + private void SetupScrollBar () { var scrollBar = new ScrollBarView (tableView, true); From b64ea4fd33abcec28a7370bbf20ba7e75298022d Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Wed, 10 May 2023 12:36:39 -0700 Subject: [PATCH 11/18] Fix ExpandLastColumn=false and make HeaderThroughline respect LineStyle --- Terminal.Gui/Views/TableView/TableView.cs | 75 ++++++++++++----------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 97f11a453a..a31d13effe 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -313,10 +313,6 @@ public override void OnDrawContent (Rect contentArea) // render the header contents if (Style.ShowHeaders && hh > 0) { var padChar = HeaderPaddingSymbol; - if (Style.ShowHorizontalHeaderThroughline && - (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { - padChar = (char)CM.Glyphs.HLine; - } var yh = hh - 1; if (Style.ShowHorizontalHeaderUnderline) { @@ -336,10 +332,32 @@ public override void OnDrawContent (Rect contentArea) Move (current.X, yh); - Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle, padChar)); + if (current.Width - colName.Length > 0 && + Style.ShowHorizontalHeaderThroughline && + (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { - if (!Style.ShowVerticalHeaderLines && !Style.ExpandLastColumn && current.IsVeryLast) { - AddRune (current.X + current.Width - 1, yh, SeparatorSymbol); + if (colName.Sum (c => Rune.ColumnWidth (c)) < current.Width) { + Driver.AddStr (colName); + } else { + Driver.AddStr (new string (colName.TakeWhile (h => (current.Width -= Rune.ColumnWidth (h)) > 0).ToArray ())); + } + } else { + Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle, padChar)); + } + + if (!Style.ExpandLastColumn) { + if (!Style.ShowVerticalHeaderLines && current.IsVeryLast) { + AddRune (current.X + current.Width - 1, yh, SeparatorSymbol); + } + if (i == columnsToRender.Length - 1) { + for (int j = current.X + current.Width; j < Bounds.Width; j++) { + Driver.SetAttribute (GetNormalColor ()); + AddRune (j, yh, ' '); + if (Style.ShowHorizontalHeaderUnderline) { + AddRune (j, yh + 1, ' '); + } + } + } } } } @@ -349,8 +367,6 @@ public override void OnDrawContent (Rect contentArea) var padChar = CellPaddingSymbol; - //ClearLine (line, width); - //work out what Row to render var rowToRender = RowOffset + (line - hh); @@ -372,18 +388,6 @@ public override void OnDrawContent (Rect contentArea) } } - /// - /// Clears a line of the console by filling it with spaces - /// - /// - /// - private void ClearLine (int row, int width) - { - Move (0, row); - Driver.SetAttribute (GetNormalColor ()); - Driver.AddStr (new string (' ', width)); - } - /// /// Returns the amount of vertical space currently occupied by the header or 0 if it is not visible. /// @@ -428,24 +432,18 @@ private void RenderCellLines (int width, int height, ColumnToRender [] columnsTo row++; } if (Style.ShowHeaders) { - var lineStyle = Style.InnerHeaderLineStyle; - if (!Style.ShowHorizontalHeaderOverline) - lineStyle = Style.OuterHeaderLineStyle; if (Style.ShowHorizontalHeaderThroughline && (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { - grid.AddLine (new Point (0, row), width, Orientation.Horizontal, lineStyle); + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.InnerHeaderLineStyle); } row++; } if (Style.ShowHorizontalHeaderUnderline) { - var lineStyle = Style.InnerHeaderLineStyle; - if (!Style.ShowHorizontalHeaderOverline && !Style.ShowHeaders) - lineStyle = Style.OuterHeaderLineStyle; - grid.AddLine (new Point (0, row), width, Orientation.Horizontal, lineStyle); + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.OuterHeaderLineStyle); row++; } - if (row > 1 && Style.ShowVerticalHeaderLines) { + if (row > 1 && Style.ShowVerticalHeaderLines && Style.InnerHeaderLineStyle != LineStyle.None) { foreach (var col in columnsToRender) { var lineStyle = Style.InnerHeaderLineStyle; if (col.X - 1 == 0) { @@ -608,6 +606,13 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen AddRune (current.X + current.Width - 1, row, SeparatorSymbol); } } + + if (!Style.ExpandLastColumn && i == columnsToRender.Length - 1) { + for (int j = current.X + current.Width; j < Bounds.Width; j++) { + Driver.SetAttribute (GetNormalColor ()); + AddRune (j, row, ' '); + } + } } } @@ -1884,27 +1889,27 @@ public class TableStyle { public Dictionary ColumnStyles { get; set; } = new Dictionary (); /// - /// The colors to use for border lines) + /// The colors to use for border lines /// public Attribute LineColor { get; set; } /// - /// The style to use for border lines within the header) + /// The style to use for border lines around the header /// public LineStyle OuterHeaderLineStyle { get; set; } = LineStyle.Single; /// - /// The style to use for border lines within the header) + /// The style to use for border lines within the header (vertical lines or throughline) /// public LineStyle InnerHeaderLineStyle { get; set; } = LineStyle.Single; /// - /// The style to use for border lines around the edge of the table) + /// The style to use for border lines around the edge of the table /// public LineStyle OuterLineStyle { get; set; } = LineStyle.Single; /// - /// The style to use for border lines within the table) + /// The style to use for border lines within the table /// public LineStyle InnerLineStyle { get; set; } = LineStyle.Single; From 979c74d1073a74310cec76e29dfc6fd7d151edcd Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Wed, 10 May 2023 12:41:01 -0700 Subject: [PATCH 12/18] Fix merge --- UICatalog/Scenarios/TableEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index a296b36bc4..fa8bbdd91e 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -90,7 +90,7 @@ public override void Setup () _miAlternatingColors = new MenuItem ("Alte_rnatingColors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked}, _miCursor = new MenuItem ("_InvertSelectedCellFirstCharacter", "", () => ToggleInvertSelectedCellFirstCharacter()){Checked = tableView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked}, new MenuItem ("ClearColumnSt_yles", "", () => ClearColumnStyles()), - new MenuItem ("Sho_wAllColumns", "", ()=>ShowAllColumns()) + new MenuItem ("Sho_wAllColumns", "", ()=>ShowAllColumns()), _miCheckboxes = new MenuItem ("_Checkboxes", "", () => ToggleCheckboxes(false)){Checked = false, CheckType = MenuItemCheckStyle.Checked }, _miRadioboxes = new MenuItem ("_Radioboxes", "", () => ToggleCheckboxes(true)){Checked = false, CheckType = MenuItemCheckStyle.Checked }, }), From ebf1c03292adffda23d4bbb5271808653f1cbae2 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Thu, 25 May 2023 08:58:04 -0700 Subject: [PATCH 13/18] Update TableStyle.cs --- Terminal.Gui/Views/TableView/TableStyle.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index d0bb1eb155..9d596525de 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -32,6 +32,11 @@ public class TableStyle { /// public bool ShowHorizontalHeaderUnderline { get; set; } = true; + /// + /// True to render a solid line through the headers (only when Overline and/or Underline are ) + /// + public bool ShowHorizontalHeaderThroughline { get; set; } = true; + /// /// True to render a solid line vertical line between cells /// From 0c0d854ca8d8baaa00f60bd9f01f12d0428f24dd Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Sat, 20 Jan 2024 16:44:58 -0800 Subject: [PATCH 14/18] Fixes after upstream changes Also: * Add BorderColor to TableStyle * Move symbols to TableStyle * Rename LineStyle to BorderStyle (at the risk of confusion with the View's border) * Comment out AlwaysUseNormalColorForVerticalCellLines feature --- Terminal.Gui/Views/TableView/TableStyle.cs | 61 +++++++++- Terminal.Gui/Views/TableView/TableView.cs | 114 +++++++----------- UICatalog/Scenarios/ListColumns.cs | 4 + UICatalog/Scenarios/TableEditor.cs | 127 ++++++++++++++++----- UnitTests/Views/TableViewTests.cs | 23 +++- UnitTests/Views/TreeTableSourceTests.cs | 3 + 6 files changed, 225 insertions(+), 107 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index 111b9675fc..51b9d0a0ad 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Terminal.Gui; @@ -9,6 +10,36 @@ namespace Terminal.Gui; /// public class TableStyle { + /// + /// Gets or sets the LineStyle for the borders surrounding header rows of a . + /// Defaults to . + /// + public LineStyle OuterHeaderBorderStyle { get; set; } = LineStyle.Single; + + /// + /// Gets or sets the LineStyle for the vertical lines separating header items in a . + /// Defaults to . + /// + public LineStyle InnerHeaderBorderStyle { get; set; } = LineStyle.Single; + + /// + /// Gets or sets the LineStyle for the borders surrounding the regular (non-header) portion of a . + /// Defaults to . + /// + public LineStyle OuterBorderStyle { get; set; } = LineStyle.Single; + + /// + /// Gets or sets the LineStyle for the lines separating regular (non-header) items in a . + /// Defaults to . + /// + public LineStyle InnerBorderStyle { get; set; } = LineStyle.Single; + + /// + /// Gets or sets the color Attribute of the inner and outer borders of a . + /// Defaults to Attribute(-1, -1) which results in . + /// + public Attribute BorderColor { get; set; } = new Attribute(-1, -1); + /// /// Gets or sets a flag indicating whether to render headers of a . /// Defaults to . @@ -69,11 +100,37 @@ public class TableStyle { /// public bool InvertSelectedCellFirstCharacter { get; set; } = false; + // TODO: Fix this, or just remove it [and SeparatorSymbol]? /// /// Gets or sets a flag indicating whether to force use when rendering /// vertical cell lines (even when is on). /// - public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false; + //public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false; + + /// + /// The symbol to add after each cell value and header value to visually seperate values (if not using vertical gridlines) + /// + //public char SeparatorSymbol { get; set; } = ' '; + + /// + /// The text representation that should be rendered for cells with the value + /// + public string NullSymbol { get; set; } = "-"; + + /// + /// The symbol to pad around values (between separators) in the header line + /// + public char HeaderPaddingSymbol { get; set; } = ' '; + + /// + /// The symbol to pad around values (between separators) + /// + public char CellPaddingSymbol { get; set; } = ' '; + + /// + /// The symbol to pad outside table (if Style.ExpandLastColumn is false) + /// + public char BackgroundSymbol { get; set; } = ' '; /// /// Collection of columns for which you want special rendering (e.g. custom column lengths, text alignment etc) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 30985e6d2d..7ee79f5235 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -146,26 +146,6 @@ public int SelectedRow { /// public int MaxCellWidth { get; set; } = DefaultMaxCellWidth; - /// - /// The text representation that should be rendered for cells with the value - /// - public string NullSymbol { get; set; } = "-"; - - /// - /// The symbol to add after each cell value and header value to visually seperate values (if not using vertical gridlines) - /// - public char SeparatorSymbol { get; set; } = ' '; - - /// - /// The symbol to pad around values (between separators) - /// - public char HeaderPaddingSymbol { get; set; } = ' '; - - /// - /// The symbol to pad around values (between separators) - /// - public char CellPaddingSymbol { get; set; } = ' '; - /// /// This event is raised when the selected cell in the table changes. /// @@ -306,10 +286,19 @@ public override void OnDrawContent (Rect contentArea) // render the cell lines grid.Clear (); - if (Style.LineColor.Initialized) - Driver.SetAttribute (Style.LineColor); - else - Driver.SetAttribute (this.ColorScheme.Normal); + Color fg; + Color bg; + if (Style.BorderColor.Foreground == -1) { + fg = this.Border.ColorScheme.Normal.Foreground; + } else { + fg = Style.BorderColor.Foreground; + } + if (Style.BorderColor.Background == -1) { + bg = this.Border.ColorScheme.Normal.Background; + } else { + bg = Style.BorderColor.Background; + } + Driver.SetAttribute (new Attribute(fg, bg)); RenderCellLines (width, Table.Rows, columnsToRender); foreach (var p in grid.GetMap (Bounds)) { @@ -337,7 +326,7 @@ public override void OnDrawContent (Rect contentArea) // render the header contents if (Style.ShowHeaders && hh > 0) { - var padChar = HeaderPaddingSymbol; + var padChar = Style.HeaderPaddingSymbol; var yh = hh - 1; if (Style.ShowHorizontalHeaderUnderline) { @@ -351,9 +340,11 @@ public override void OnDrawContent (Rect contentArea) var colStyle = Style.GetColumnStyleIfAny (current.Column); var colName = table.ColumnNames [current.Column]; + /* if (!Style.ShowVerticalHeaderLines && current.X - 1 >= 0) { - AddRune (current.X - 1, yh, SeparatorSymbol); + AddRune (current.X - 1, yh, (Rune)Style.SeparatorSymbol); } + */ Move (current.X, yh); @@ -361,25 +352,27 @@ public override void OnDrawContent (Rect contentArea) Style.ShowHorizontalHeaderThroughline && (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { - if (colName.Sum (c => Rune.ColumnWidth (c)) < current.Width) { + if (colName.Sum (c => ((Rune)c).GetColumns ()) < current.Width) { Driver.AddStr (colName); } else { - Driver.AddStr (new string (colName.TakeWhile (h => (current.Width -= Rune.ColumnWidth (h)) > 0).ToArray ())); + Driver.AddStr (new string (colName.TakeWhile (h => (current.Width -= ((Rune)h).GetColumns ()) > 0).ToArray ())); } } else { Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle, padChar)); } if (!Style.ExpandLastColumn) { + /* if (!Style.ShowVerticalHeaderLines && current.IsVeryLast) { - AddRune (current.X + current.Width - 1, yh, SeparatorSymbol); + AddRune (current.X + current.Width - 1, yh, (Rune)Style.SeparatorSymbol); } + */ if (i == columnsToRender.Length - 1) { for (int j = current.X + current.Width; j < Bounds.Width; j++) { Driver.SetAttribute (GetNormalColor ()); - AddRune (j, yh, ' '); + AddRune (j, yh, (Rune)Style.BackgroundSymbol); if (Style.ShowHorizontalHeaderUnderline) { - AddRune (j, yh + 1, ' '); + AddRune (j, yh + 1, (Rune)Style.BackgroundSymbol); } } } @@ -390,7 +383,7 @@ public override void OnDrawContent (Rect contentArea) // render the cell contents for (var line = hh; line < frame.Height; line++) { - var padChar = CellPaddingSymbol; + var padChar = Style.CellPaddingSymbol; //work out what Row to render var rowToRender = RowOffset + (line - hh); @@ -453,30 +446,30 @@ private void RenderCellLines (int width, int height, ColumnToRender [] columnsTo if (hh > 0) { if (Style.ShowHorizontalHeaderOverline) { - grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.OuterHeaderLineStyle); + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.OuterHeaderBorderStyle); row++; } if (Style.ShowHeaders) { if (Style.ShowHorizontalHeaderThroughline && (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { - grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.InnerHeaderLineStyle); + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.InnerHeaderBorderStyle); } row++; } if (Style.ShowHorizontalHeaderUnderline) { - grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.OuterHeaderLineStyle); + grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.OuterHeaderBorderStyle); row++; } - if (row > 1 && Style.ShowVerticalHeaderLines && Style.InnerHeaderLineStyle != LineStyle.None) { + if (row > 1 && Style.ShowVerticalHeaderLines && Style.InnerHeaderBorderStyle != LineStyle.None) { foreach (var col in columnsToRender) { - var lineStyle = Style.InnerHeaderLineStyle; + var lineStyle = Style.InnerHeaderBorderStyle; if (col.X - 1 == 0) { - lineStyle = Style.OuterHeaderLineStyle; + lineStyle = Style.OuterHeaderBorderStyle; } grid.AddLine (new Point (col.X - 1, 0), row, Orientation.Vertical, lineStyle); } - grid.AddLine (new Point (width - 1, 0), row, Orientation.Vertical, Style.OuterHeaderLineStyle); + grid.AddLine (new Point (width - 1, 0), row, Orientation.Vertical, Style.OuterHeaderBorderStyle); } } @@ -487,18 +480,18 @@ private void RenderCellLines (int width, int height, ColumnToRender [] columnsTo // render the vertical cell lines if (Style.ShowVerticalCellLines) { foreach (var col in columnsToRender) { - var lineStyle = Style.InnerLineStyle; + var lineStyle = Style.InnerBorderStyle; if (col.X - 1 == 0) { - lineStyle = Style.OuterLineStyle; + lineStyle = Style.OuterBorderStyle; } grid.AddLine (new Point (col.X - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, lineStyle); } - grid.AddLine (new Point (width - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, Style.OuterLineStyle); + grid.AddLine (new Point (width - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, Style.OuterBorderStyle); } // render the bottom line if (Style.ShowHorizontalBottomline) { - grid.AddLine (new Point (0, height - RowOffset + hh - 1), width, Orientation.Horizontal, Style.OuterLineStyle); + grid.AddLine (new Point (0, height - RowOffset + hh - 1), width, Orientation.Horizontal, Style.OuterBorderStyle); } } @@ -552,9 +545,6 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen color = Enabled ? rowScheme.Normal : rowScheme.Disabled; } - //Driver.SetAttribute (color); - //Driver.AddStr (new string (' ', Bounds.Width)); - // Render cells for each visible header for the current row for (int i = 0; i < columnsToRender.Length; i++) { @@ -606,7 +596,7 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen RenderCell (cellColor, render, isPrimaryCell); // Style.AlwaysUseNormalColorForVerticalCellLines is no longer possible after switch to LineCanvas - // except when cell lines are disabled and SeparatorSymbol is used + // except when cell lines are disabled and Style.SeparatorSymbol is used if (!Style.ShowVerticalCellLines) { if (isSelectedCell) { @@ -617,36 +607,10 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen Driver.SetAttribute (color); } - // If not in full row select mode always, reset color scheme to normal and render the vertical line (or space) at the end of the cell - if (!FullRowSelect) - Driver.SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled); - - if (Style.AlwaysUseNormalColorForVerticalCellLines) { - color = rowScheme.Normal; - - } else if (scheme != rowScheme) { - - // Reset color scheme to normal for drawing separators if we drew text with custom scheme - if (isSelectedCell) { - color = focused ? rowScheme.HotFocus : rowScheme.HotNormal; - } else { - color = Enabled ? rowScheme.Normal : rowScheme.Disabled; - } - } - Driver.SetAttribute (color); - - if (current.X - 1 >= 0) { - AddRune (current.X - 1, row, SeparatorSymbol); - } - if (!Style.ExpandLastColumn && current.IsVeryLast) { - AddRune (current.X + current.Width - 1, row, SeparatorSymbol); - } - } - if (!Style.ExpandLastColumn && i == columnsToRender.Length - 1) { for (int j = current.X + current.Width; j < Bounds.Width; j++) { Driver.SetAttribute (GetNormalColor ()); - AddRune (j, row, ' '); + AddRune (j, row, (Rune)Style.BackgroundSymbol); } } } @@ -1765,7 +1729,7 @@ private int CalculateMaxCellWidth (int col, int rowsToRender, ColumnStyle colSty private string GetRepresentation (object value, ColumnStyle colStyle) { if (value == null || value == DBNull.Value) { - return string.IsNullOrEmpty(NullSymbol) ? " " : NullSymbol; + return string.IsNullOrEmpty(Style.NullSymbol) ? " " : Style.NullSymbol; } return colStyle != null ? colStyle.GetRepresentation (value) : value.ToString (); diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 5e8983d673..ac66eb50f3 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -73,12 +73,14 @@ public override void Setup () Checked = _listColView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked }, + /* _miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines ()) { Checked = _listColView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + */ _miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) { Checked = _listColView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked @@ -218,6 +220,7 @@ void ToggleExpandLastColumn () } + /* void ToggleAlwaysUseNormalColorForVerticalCellLines () { _miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked; @@ -225,6 +228,7 @@ void ToggleAlwaysUseNormalColorForVerticalCellLines () _listColView.Update (); } + */ void ToggleSmoothScrolling () { diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index e12951be5c..f1b979dfb4 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -29,7 +29,7 @@ public class TableEditor : Scenario { private MenuItem _miCellLines; private MenuItem _miFullRowSelect; private MenuItem _miExpandLastColumn; - private MenuItem _miAlwaysUseNormalColorForVerticalCellLines; + //private MenuItem _miAlwaysUseNormalColorForVerticalCellLines; private MenuItem _miSmoothScrolling; private MenuItem _miAlternatingColors; private MenuItem _miCursor; @@ -43,12 +43,12 @@ public class TableEditor : Scenario { ColorScheme redColorSchemeAlt; ColorScheme alternatingColorScheme; - enum TableLineStyleType + enum TableBorderStyleType { - OuterHeaderLineStyle, - InnerHeaderLineStyle, - OuterLineStyle, - InnerLineStyle, + OuterHeaderBorderStyle, + InnerHeaderBorderStyle, + OuterBorderStyle, + InnerBorderStyle, } HashSet _checkedFileSystemInfos = new HashSet (); @@ -86,10 +86,11 @@ public override void Setup () _miShowHorizontalScrollIndicators = new MenuItem ("Horizontal_ScrollIndicators", "", () => ToggleHorizontalScrollIndicators()){Checked = tableView.Style.ShowHorizontalScrollIndicators, CheckType = MenuItemCheckStyle.Checked }, _miFullRowSelect =new MenuItem ("_FullRowSelect", "", () => ToggleFullRowSelect()){Checked = tableView.FullRowSelect, CheckType = MenuItemCheckStyle.Checked }, _miCellLines =new MenuItem ("_CellLines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, - _miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("AlwaysUse_NormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines()){Checked = tableView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, - new MenuItem ("_LineStyles", "", () => SetLineStyles()), - new MenuItem ("AllLines", "", () => ToggleAllCellLines()), - new MenuItem ("NoLines", "", () => ToggleNoCellLines()), + //_miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("AlwaysUse_NormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines()){Checked = tableView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + new MenuItem ("_BorderStyles", "", () => SetBorderStyles()), + new MenuItem ("_BorderColor", "", () => SetBorderColor()), + new MenuItem ("AllBorders", "", () => ToggleAllCellLines()), + new MenuItem ("NoBorders", "", () => ToggleNoCellLines()), _miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn()){Checked = tableView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked }, _miSmoothScrolling = new MenuItem ("S_moothHorizontalScrolling", "", () => ToggleSmoothScrolling()){Checked = tableView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked }, _miAlternatingColors = new MenuItem ("Alte_rnatingColors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked}, @@ -366,20 +367,20 @@ private void RunColumnWidthDialog (int? col, string prompt, Action NStack.ustring.Make (e.ToString ())).ToArray ()) { + e => e.ToString ()).ToArray ()) { X = 0, Y = 0, }; @@ -393,7 +394,7 @@ private void SetLineStyles () var borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast ().ToList (); var rbBorderStyle = new RadioGroup (borderStyleEnum.Select ( - e => NStack.ustring.Make (e.ToString ())).ToArray ()) { + e => e.ToString ()).ToArray ()) { X = Pos.Right (rbBorderType) + 2, Y = 0, SelectedItem = (int) chosenStyle @@ -401,25 +402,25 @@ private void SetLineStyles () // Change selection to existing style value for given type rbBorderType.SelectedItemChanged += (s, e) => { - chosenType = (TableLineStyleType) e.SelectedItem; + chosenType = (TableBorderStyleType)e.SelectedItem; var str = chosenType.ToString (); switch (e.SelectedItem) { case 0: - rbBorderStyle.SelectedItem = (int) (TableLineStyleType) style.OuterHeaderLineStyle; + rbBorderStyle.SelectedItem = (int) (TableBorderStyleType) style.OuterHeaderBorderStyle; break; case 1: - rbBorderStyle.SelectedItem = (int) (TableLineStyleType) style.InnerHeaderLineStyle; + rbBorderStyle.SelectedItem = (int) (TableBorderStyleType) style.InnerHeaderBorderStyle; break; case 2: - rbBorderStyle.SelectedItem = (int) (TableLineStyleType) style.OuterLineStyle; + rbBorderStyle.SelectedItem = (int) (TableBorderStyleType) style.OuterBorderStyle; break; case 3: - rbBorderStyle.SelectedItem = (int) (TableLineStyleType) style.InnerLineStyle; + rbBorderStyle.SelectedItem = (int) (TableBorderStyleType) style.InnerBorderStyle; break; } }; rbBorderStyle.SelectedItemChanged += (s, e) => { - chosenStyle = (LineStyle) e.SelectedItem; + chosenStyle = (LineStyle)e.SelectedItem; }; d.Add (rbBorderType, rbBorderStyle); @@ -431,19 +432,19 @@ private void SetLineStyles () try { switch ((int)chosenType) { case 0: - Action setterHdrOut = (s, v) => s.OuterHeaderLineStyle = v; + Action setterHdrOut = (s, v) => s.OuterHeaderBorderStyle = v; setterHdrOut (style, chosenStyle); break; case 1: - Action setterHdrIn = (s, v) => s.InnerHeaderLineStyle = v; + Action setterHdrIn = (s, v) => s.InnerHeaderBorderStyle = v; setterHdrIn (style, chosenStyle); break; case 2: - Action setterOut = (s, v) => s.OuterLineStyle = v; + Action setterOut = (s, v) => s.OuterBorderStyle = v; setterOut (style, chosenStyle); break; case 3: - Action setterIn = (s, v) => s.InnerLineStyle = v; + Action setterIn = (s, v) => s.InnerBorderStyle = v; setterIn (style, chosenStyle); break; } @@ -455,6 +456,75 @@ private void SetLineStyles () } } + private void SetBorderColor () + { + var style = tableView.Style; + Color chosenForeground; + Color chosenBackground; + if (style.BorderColor.Foreground != -1) { + chosenForeground = style.BorderColor.Foreground; + } else { + chosenForeground = tableView.Border.ColorScheme.Normal.Foreground; + } + if (style.BorderColor.Background != -1) { + chosenBackground = style.BorderColor.Background; + } else { + chosenBackground = tableView.Border.ColorScheme.Normal.Background; + } + + var accepted = false; + var ok = new Button ("Ok", is_default: true); + ok.Clicked += (s, e) => { accepted = true; Application.RequestStop (); }; + var cancel = new Button ("Cancel"); + cancel.Clicked += (s, e) => { Application.RequestStop (); }; + var d = new Dialog (ok, cancel) { Title = "BorderColor" }; + + var colorEnum = Enum.GetNames (typeof (ColorName)); + var rbBorderFgTitle = new Label ("Foreground") { + X = 0, + Y = 0 + }; + var rbBorderForeground = new RadioGroup (colorEnum.Select ( + e => e.ToString ()).ToArray ()) { + X = 0, + Y = Pos.Bottom(rbBorderFgTitle), + SelectedItem = (int)chosenForeground + }; + var rbBorderBgTitle = new Label ("Background") { + X = Pos.Right (rbBorderForeground) + 2, + Y = 0 + }; + var rbBorderBackground = new RadioGroup (colorEnum.Select ( + e => e.ToString ()).ToArray ()) { + X = Pos.Right (rbBorderForeground) + 2, + Y = Pos.Bottom(rbBorderBgTitle), + SelectedItem = (int)chosenBackground + }; + + rbBorderForeground.SelectedItemChanged += (s, e) => { + Color.TryParse (colorEnum [e.SelectedItem], out var c); chosenForeground = c; + }; + rbBorderBackground.SelectedItemChanged += (s, e) => { + Color.TryParse (colorEnum [e.SelectedItem], out var c); chosenBackground = c; + }; + + d.Add (rbBorderFgTitle, rbBorderForeground, rbBorderBgTitle, rbBorderBackground); + + Application.Run (d); + + if (accepted) { + + try { + Action setterCol = (s, v) => s.BorderColor = v; + setterCol (style, new Attribute (chosenForeground, chosenBackground)); + } catch (Exception ex) { + MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok"); + } + + tableView.Update (); + } + } + private void SetupScrollBar () { var scrollBar = new ScrollBarView (tableView, true); @@ -638,6 +708,7 @@ private void CheckOrUncheckFile (FileSystemInfo info, bool check) } } + /* private void ToggleAlwaysUseNormalColorForVerticalCellLines () { _miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked; @@ -645,6 +716,8 @@ private void ToggleAlwaysUseNormalColorForVerticalCellLines () tableView.Update (); } + */ + private void ToggleSmoothScrolling () { _miSmoothScrolling.Checked = !_miSmoothScrolling.Checked; diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index 1ed27c9141..11357826ab 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -566,11 +566,18 @@ public void TableView_ExpandLastColumn_False () tv.Draw (); string expected = @" +┌─┬─┐ +│A│B│ +├─┼─┤ +│1│2│ +"; +// TODO: Which one should be the correct behavior? + /*string expected = @" ┌─┬─┬────┐ │A│B│ │ ├─┼─┼────┤ │1│2│ │ -"; +";*/ TestHelpers.AssertDriverContentsAre (expected, output); // Shutdown must be called to safely clean up Application if Init has been called @@ -1958,11 +1965,19 @@ public void LongColumnTest () tableView.Draw (); expected = @" +│A │B │Very Long │ +├───┼───┼──────────┤ +│1 │2 │aaaaaaaaaa│ +│1 │2 │aaa │ +"; +// TODO: Which one should be the correct behavior? + /*expected = +@" │A │B │Very Long │ │ ├───┼───┼──────────┼────┤ │1 │2 │aaaaaaaaaa│ │ │1 │2 │aaa │ │ -"; +";*/ TestHelpers.AssertDriverContentsAre (expected, output); // MaxCellWidth limits MinCellWidth @@ -2075,7 +2090,7 @@ public void CellEventsBackgroundFill () dt.Rows.Add ("Hello", DBNull.Value, "f"); tv.Table = new DataTableSource (dt); - tv.NullSymbol = string.Empty; + tv.Style.NullSymbol = string.Empty; Application.Top.Add (tv); Application.Begin (Application.Top); @@ -2251,6 +2266,7 @@ public void TestFullRowSelect_SelectionColorStopsAtTableEdge_WithCellLines () TestHelpers.AssertDriverAttributesAre (expected, driver: Application.Driver, normal, focus); } + /* [Fact, AutoInitShutdown] public void TestFullRowSelect_AlwaysUseNormalColorForVerticalCellLines () { @@ -2310,6 +2326,7 @@ public void TestFullRowSelect_AlwaysUseNormalColorForVerticalCellLines () TestHelpers.AssertDriverAttributesAre (expected, driver: Application.Driver, normal, focus); } + */ [Fact, AutoInitShutdown] public void TestTableViewCheckboxes_Simple () diff --git a/UnitTests/Views/TreeTableSourceTests.cs b/UnitTests/Views/TreeTableSourceTests.cs index b5563984fe..8007b7330e 100644 --- a/UnitTests/Views/TreeTableSourceTests.cs +++ b/UnitTests/Views/TreeTableSourceTests.cs @@ -28,6 +28,7 @@ public void TestTreeTableSource_BasicExpanding_WithKeyboard () { var tv = GetTreeTable (out _); + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1; tv.Draw (); @@ -85,6 +86,7 @@ public void TestTreeTableSource_BasicExpanding_WithMouse () { var tv = GetTreeTable (out _); + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1; tv.Draw (); @@ -150,6 +152,7 @@ public void TestTreeTableSource_CombinedWithCheckboxes () CheckBoxTableSourceWrapperByIndex checkSource; tv.Table = checkSource = new CheckBoxTableSourceWrapperByIndex (tv, tv.Table); + tv.Style.ShowHorizontalHeaderThroughline = false; tv.Style.GetOrCreateColumnStyle (2).MinAcceptableWidth = 1; tv.Draw (); From 5a055f09fc6cbb9db5c9507bf3de3e323638fe80 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Mon, 29 Jan 2024 11:37:28 -0800 Subject: [PATCH 15/18] New features, including return of empty column behavior * TODO: Fix new unit tests * Add new AddEmptyColumn (when ExpandLastColumn is false) added as a new option (default true) * Add new InvertSelectedCell (the entire cell vs. existing option for first character only) * Add new HeaderSeparatorSymbol distinct from SeparatorSymbol for regular cells --- Terminal.Gui/Views/TableView/TableStyle.cs | 42 ++++- Terminal.Gui/Views/TableView/TableView.cs | 135 ++++++++++----- UICatalog/Scenarios/TableEditor.cs | 38 ++++- UnitTests/Views/TableViewTests.cs | 189 +++++++++++++++++++-- 4 files changed, 331 insertions(+), 73 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index 51b9d0a0ad..370ab368ce 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -66,7 +66,7 @@ public class TableStyle { /// /// True to render a solid line through the headers (only when Overline and/or Underline are ) /// - public bool ShowHorizontalHeaderThroughline { get; set; } = true; + public bool ShowHorizontalHeaderThroughline { get; set; } = false; /// /// True to render a solid line vertical line between cells @@ -93,6 +93,13 @@ public class TableStyle { /// public bool ShowHorizontalBottomline { get; set; } = false; + /// + /// True to invert the colors of the entire selected cell in the . + /// Helpful for when is on, especially when the doesn't show + /// the cursor + /// + public bool InvertSelectedCell { get; set; } = false; + /// /// True to invert the colors of the first symbol of the selected cell in the . /// This gives the appearance of a cursor for when the doesn't otherwise show @@ -100,7 +107,10 @@ public class TableStyle { /// public bool InvertSelectedCellFirstCharacter { get; set; } = false; - // TODO: Fix this, or just remove it [and SeparatorSymbol]? + // NOTE: This is equivalent to True by default after change to LineCanvas borders and can't be turned off + // without disabling ShowVerticalCellLines, however SeparatorSymbol and HeaderSeparatorSymbol could be + // used to approximate the previous default behavior with FullRowSelect + // TODO: Explore ways of changing this without a workaround /// /// Gets or sets a flag indicating whether to force use when rendering /// vertical cell lines (even when is on). @@ -108,9 +118,14 @@ public class TableStyle { //public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false; /// - /// The symbol to add after each cell value and header value to visually seperate values (if not using vertical gridlines) + /// The symbol to add after each header value to visually seperate values (if not using vertical gridlines) + /// + public char HeaderSeparatorSymbol { get; set; } = ' '; + + /// + /// The symbol to add after each cell value to visually seperate values (if not using vertical gridlines) /// - //public char SeparatorSymbol { get; set; } = ' '; + public char SeparatorSymbol { get; set; } = ' '; /// /// The text representation that should be rendered for cells with the value @@ -128,7 +143,8 @@ public class TableStyle { public char CellPaddingSymbol { get; set; } = ' '; /// - /// The symbol to pad outside table (if Style.ExpandLastColumn is false) + /// The symbol to pad outside table (if both and + /// are False) /// public char BackgroundSymbol { get; set; } = ' '; @@ -144,15 +160,25 @@ public class TableStyle { public RowColorGetterDelegate RowColorGetter { get; set; } /// - /// Determines rendering when the last column in the table is visible but it's + /// Determines rendering when the last column in the table is visible but its /// content or is less than the remaining /// space in the control. True (the default) will expand the column to fill - /// the remaining bounds of the control. False will draw a column ending line - /// and leave a blank column that cannot be selected in the remaining space. + /// the remaining bounds of the control. If false, + /// determines the behavior of the remaining space. /// /// public bool ExpandLastColumn { get; set; } = true; + /// + /// Determines rendering when the last column in the table is visible but its + /// content or is less than the remaining + /// space in the control *and* is False. True (the default) + /// will add a blank column that cannot be selected in the remaining space. + /// False will fill the remaining space with . + /// + /// + public bool AddEmptyColumn { get; set; } = true; + /// /// /// Determines how is updated when scrolling diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 7ee79f5235..b48544a086 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -299,30 +299,31 @@ public override void OnDrawContent (Rect contentArea) bg = Style.BorderColor.Background; } Driver.SetAttribute (new Attribute(fg, bg)); - RenderCellLines (width, Table.Rows, columnsToRender); + + var lineWidth = width; + + if (!Style.ExpandLastColumn && Style.AddEmptyColumn) { + lineWidth = contentArea.Width; + } + RenderCellLines (lineWidth, Table.Rows, columnsToRender); foreach (var p in grid.GetMap (Bounds)) { this.AddRune (p.Key.X, p.Key.Y, p.Value); } - // render arrows - scrollRightPoint = null; - scrollLeftPoint = null; int hh = GetHeaderHeightIfAny (); + + // render arrows if (Style.ShowHorizontalScrollIndicators) { if (hh > 0 && MoreColumnsToLeft ()) { scrollLeftPoint = new Point (0, hh); + AddRuneAt (Driver, 0, scrollLeftPoint.Value.Y - 1, CM.Glyphs.LeftArrow); } if (hh > 0 && MoreColumnsToRight (columnsToRender)) { - scrollRightPoint = new Point (width - 1, hh); + scrollRightPoint = new Point (lineWidth - 1, hh); + AddRuneAt (Driver, scrollRightPoint.Value.X, scrollRightPoint.Value.Y - 1, CM.Glyphs.RightArrow); } } - if (scrollLeftPoint != null) { - AddRuneAt (Driver, 0, scrollLeftPoint.Value.Y - 1, CM.Glyphs.LeftArrow); - } - if (scrollRightPoint != null) { - AddRuneAt (Driver, scrollRightPoint.Value.X, scrollRightPoint.Value.Y - 1, CM.Glyphs.RightArrow); - } // render the header contents if (Style.ShowHeaders && hh > 0) { @@ -340,17 +341,15 @@ public override void OnDrawContent (Rect contentArea) var colStyle = Style.GetColumnStyleIfAny (current.Column); var colName = table.ColumnNames [current.Column]; - /* - if (!Style.ShowVerticalHeaderLines && current.X - 1 >= 0) { - AddRune (current.X - 1, yh, (Rune)Style.SeparatorSymbol); + if (!Style.ShowVerticalHeaderLines && current.X > 0) { + AddRune (current.X - 1, yh, (Rune)Style.HeaderSeparatorSymbol); } - */ Move (current.X, yh); - if (current.Width - colName.Length > 0 && - Style.ShowHorizontalHeaderThroughline && - (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { + if (current.Width - colName.Length > 0 + && Style.ShowHorizontalHeaderThroughline + && (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { if (colName.Sum (c => ((Rune)c).GetColumns ()) < current.Width) { Driver.AddStr (colName); @@ -361,20 +360,28 @@ public override void OnDrawContent (Rect contentArea) Driver.AddStr (TruncateOrPad (colName, colName, current.Width, colStyle, padChar)); } + if (!Style.ShowVerticalHeaderLines + && current.Column == columnsToRender.First().Column + columnsToRender.Length - 1 + && current.X + current.Width - 1 <= lineWidth) { + AddRune (current.X + current.Width - 1, yh, (Rune)Style.HeaderSeparatorSymbol); + } + if (!Style.ExpandLastColumn) { - /* - if (!Style.ShowVerticalHeaderLines && current.IsVeryLast) { - AddRune (current.X + current.Width - 1, yh, (Rune)Style.SeparatorSymbol); - } - */ - if (i == columnsToRender.Length - 1) { - for (int j = current.X + current.Width; j < Bounds.Width; j++) { - Driver.SetAttribute (GetNormalColor ()); - AddRune (j, yh, (Rune)Style.BackgroundSymbol); - if (Style.ShowHorizontalHeaderUnderline) { - AddRune (j, yh + 1, (Rune)Style.BackgroundSymbol); + if (!Style.AddEmptyColumn) { + if (i == columnsToRender.Length - 1) { + for (int j = current.X + current.Width; j < Bounds.Width; j++) { + Driver.SetAttribute (GetNormalColor ()); + AddRune (j, yh, (Rune)Style.BackgroundSymbol); + if (Style.ShowHorizontalHeaderOverline) { + AddRune (j, yh - 1, (Rune)Style.BackgroundSymbol); + } + if (Style.ShowHorizontalHeaderUnderline) { + AddRune (j, yh + 1, (Rune)Style.BackgroundSymbol); + } } } + } else if (!Style.ShowVerticalHeaderLines) { + AddRune (Bounds.Width - 1, yh, (Rune)Style.HeaderSeparatorSymbol); } } } @@ -394,11 +401,16 @@ public override void OnDrawContent (Rect contentArea) // No more data if (rowToRender >= Table.Rows) { - /* - if (rowToRender == Table.Rows.Count && Style.ShowHorizontalBottomline) { - RenderBottomLine (line, width, columnsToRender); + if (rowToRender == Table.Rows + && Style.ShowHorizontalBottomline + && !Style.ExpandLastColumn + && !Style.AddEmptyColumn) { + var start = columnsToRender [^1]; + for (int i = start.X + start.Width; i < Bounds.Width; i++) { + AddRune (i, Table.Rows + hh, (Rune)Style.BackgroundSymbol); + } } - */ + continue; } @@ -468,7 +480,16 @@ private void RenderCellLines (int width, int height, ColumnToRender [] columnsTo lineStyle = Style.OuterHeaderBorderStyle; } grid.AddLine (new Point (col.X - 1, 0), row, Orientation.Vertical, lineStyle); + + // left side of empty column + if (col.Column == columnsToRender.First().Column + columnsToRender.Length - 1 + && !Style.ExpandLastColumn + && Style.AddEmptyColumn) { + grid.AddLine (new Point (col.X + col.Width - 1, 0), row, Orientation.Vertical, lineStyle); + } } + + // right side grid.AddLine (new Point (width - 1, 0), row, Orientation.Vertical, Style.OuterHeaderBorderStyle); } } @@ -485,6 +506,13 @@ private void RenderCellLines (int width, int height, ColumnToRender [] columnsTo lineStyle = Style.OuterBorderStyle; } grid.AddLine (new Point (col.X - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, lineStyle); + + // left side of empty column + if (col.Column == columnsToRender.First().Column + columnsToRender.Length - 1 + && !Style.ExpandLastColumn + && Style.AddEmptyColumn) { + grid.AddLine (new Point (col.X + col.Width - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, lineStyle); + } } grid.AddLine (new Point (width - 1, row - 1), height - RowOffset + 1, Orientation.Vertical, Style.OuterBorderStyle); } @@ -596,9 +624,23 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen RenderCell (cellColor, render, isPrimaryCell); // Style.AlwaysUseNormalColorForVerticalCellLines is no longer possible after switch to LineCanvas - // except when cell lines are disabled and Style.SeparatorSymbol is used + // except when vertical cell lines are disabled (though a Style.SeparatorSymbol could be used) if (!Style.ShowVerticalCellLines) { + if (isSelectedCell && FullRowSelect) { + color = focused ? rowScheme.Focus : rowScheme.HotNormal; + } else { + color = Enabled ? rowScheme.Normal : rowScheme.Disabled; + } + Driver.SetAttribute (color); + + if (current.X > 0) { + AddRune (current.X - 1, row, (Rune)Style.SeparatorSymbol); + } + if (current.X + current.Width - 1 < Bounds.Width) { + AddRune (current.X + current.Width - 1, row, (Rune)Style.SeparatorSymbol); + } + if (isSelectedCell) { color = focused ? rowScheme.Focus : rowScheme.HotNormal; } else { @@ -607,10 +649,17 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen Driver.SetAttribute (color); } - if (!Style.ExpandLastColumn && i == columnsToRender.Length - 1) { - for (int j = current.X + current.Width; j < Bounds.Width; j++) { - Driver.SetAttribute (GetNormalColor ()); - AddRune (j, row, (Rune)Style.BackgroundSymbol); + if (!Style.ExpandLastColumn) { + if (!Style.AddEmptyColumn) { + if (i == columnsToRender.Length - 1) { + for (int j = current.X + current.Width; j < Bounds.Width; j++) { + Driver.SetAttribute (GetNormalColor ()); + AddRune (j, row, (Rune)Style.BackgroundSymbol); + } + } + } else if (!Style.ShowVerticalCellLines) { + Driver.SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled); + AddRune (Bounds.Width - 1, row, (Rune)Style.SeparatorSymbol); } } } @@ -631,7 +680,7 @@ protected virtual void RenderCell (Attribute cellColor, string render, bool isPr // If the cell is the selected col/row then draw the first rune in inverted colors // this allows the user to track which cell is the active one during a multi cell // selection or in full row select mode - if (Style.InvertSelectedCellFirstCharacter && isPrimaryCell) { + if ((Style.InvertSelectedCell || Style.InvertSelectedCellFirstCharacter) && isPrimaryCell) { if (render.Length > 0) { // invert the color of the current cell for the first character @@ -639,8 +688,10 @@ protected virtual void RenderCell (Attribute cellColor, string render, bool isPr Driver.AddRune ((Rune)render [0]); if (render.Length > 1) { - Driver.SetAttribute (cellColor); - Driver.AddStr (render.Substring (1)); + if (!Style.InvertSelectedCell) { + Driver.SetAttribute (cellColor); + } + Driver.AddStr (render[1..]); } } } else { @@ -1728,7 +1779,7 @@ private int CalculateMaxCellWidth (int col, int rowsToRender, ColumnStyle colSty /// private string GetRepresentation (object value, ColumnStyle colStyle) { - if (value == null || value == DBNull.Value) { + if (value is null || value == DBNull.Value) { return string.IsNullOrEmpty(Style.NullSymbol) ? " " : Style.NullSymbol; } diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs index f1b979dfb4..cf49f4c9c0 100644 --- a/UICatalog/Scenarios/TableEditor.cs +++ b/UICatalog/Scenarios/TableEditor.cs @@ -29,9 +29,11 @@ public class TableEditor : Scenario { private MenuItem _miCellLines; private MenuItem _miFullRowSelect; private MenuItem _miExpandLastColumn; + private MenuItem _miAddEmptyColumn; //private MenuItem _miAlwaysUseNormalColorForVerticalCellLines; private MenuItem _miSmoothScrolling; private MenuItem _miAlternatingColors; + private MenuItem _miInvert; private MenuItem _miCursor; private MenuItem _miBottomline; private MenuItem _miCheckboxes; @@ -85,15 +87,17 @@ public override void Setup () _miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline()){Checked = tableView.Style.ShowHorizontalBottomline, CheckType = MenuItemCheckStyle.Checked }, _miShowHorizontalScrollIndicators = new MenuItem ("Horizontal_ScrollIndicators", "", () => ToggleHorizontalScrollIndicators()){Checked = tableView.Style.ShowHorizontalScrollIndicators, CheckType = MenuItemCheckStyle.Checked }, _miFullRowSelect =new MenuItem ("_FullRowSelect", "", () => ToggleFullRowSelect()){Checked = tableView.FullRowSelect, CheckType = MenuItemCheckStyle.Checked }, - _miCellLines =new MenuItem ("_CellLines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, + _miCellLines =new MenuItem ("Cell_Lines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, //_miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("AlwaysUse_NormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines()){Checked = tableView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, new MenuItem ("_BorderStyles", "", () => SetBorderStyles()), new MenuItem ("_BorderColor", "", () => SetBorderColor()), new MenuItem ("AllBorders", "", () => ToggleAllCellLines()), new MenuItem ("NoBorders", "", () => ToggleNoCellLines()), _miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn()){Checked = tableView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked }, + _miAddEmptyColumn = new MenuItem ("A_ddEmptyColumn", "", () => ToggleAddEmptyColumn()){Checked = tableView.Style.AddEmptyColumn, CheckType = MenuItemCheckStyle.Checked }, _miSmoothScrolling = new MenuItem ("S_moothHorizontalScrolling", "", () => ToggleSmoothScrolling()){Checked = tableView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked }, _miAlternatingColors = new MenuItem ("Alte_rnatingColors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked}, + _miInvert = new MenuItem ("InvertSelectedCell", "", () => ToggleInvertSelectedCell()){Checked = tableView.Style.InvertSelectedCell,CheckType = MenuItemCheckStyle.Checked}, _miCursor = new MenuItem ("_InvertSelectedCellFirstCharacter", "", () => ToggleInvertSelectedCellFirstCharacter()){Checked = tableView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked}, new MenuItem ("ClearColumnSt_yles", "", () => ClearColumnStyles()), new MenuItem ("Sho_wAllColumns", "", ()=>ShowAllColumns()), @@ -288,11 +292,13 @@ private void HideColumn (int clickedCol) private int? GetColumn () { - if (tableView.Table == null) + if (tableView.Table == null) { return null; + } - if (tableView.SelectedColumn < 0 || tableView.SelectedColumn > tableView.Table.Columns) + if (tableView.SelectedColumn < 0 || tableView.SelectedColumn > tableView.Table.Columns) { return null; + } return tableView.SelectedColumn; } @@ -502,10 +508,14 @@ private void SetBorderColor () }; rbBorderForeground.SelectedItemChanged += (s, e) => { - Color.TryParse (colorEnum [e.SelectedItem], out var c); chosenForeground = c; + if (Color.TryParse (colorEnum [e.SelectedItem], out var c)) { + chosenForeground = (Color)c; + } }; rbBorderBackground.SelectedItemChanged += (s, e) => { - Color.TryParse (colorEnum [e.SelectedItem], out var c); chosenBackground = c; + if (Color.TryParse (colorEnum [e.SelectedItem], out var c)) { + chosenBackground = (Color)c; + } }; d.Add (rbBorderFgTitle, rbBorderForeground, rbBorderBgTitle, rbBorderBackground); @@ -648,7 +658,14 @@ private void ToggleExpandLastColumn () { _miExpandLastColumn.Checked = !_miExpandLastColumn.Checked; tableView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked; + tableView.Update (); + + } + private void ToggleAddEmptyColumn () + { + _miAddEmptyColumn.Checked = !_miAddEmptyColumn.Checked; + tableView.Style.AddEmptyColumn = (bool)_miAddEmptyColumn.Checked; tableView.Update (); } @@ -739,12 +756,14 @@ private void ToggleAllCellLines () tableView.Style.ShowHorizontalHeaderUnderline = true; tableView.Style.ShowVerticalHeaderLines = true; tableView.Style.ShowVerticalCellLines = true; + tableView.Style.ShowHorizontalBottomline = true; _miHeaderOverline.Checked = true; _miHeaderThruline.Checked = true; _miHeaderUnderline.Checked = true; _miHeaderVertline.Checked = true; _miCellLines.Checked = true; + _miBottomline.Checked = true; tableView.Update (); } @@ -755,12 +774,14 @@ private void ToggleNoCellLines () tableView.Style.ShowHorizontalHeaderUnderline = false; tableView.Style.ShowVerticalHeaderLines = false; tableView.Style.ShowVerticalCellLines = false; + tableView.Style.ShowHorizontalBottomline = false; _miHeaderOverline.Checked = false; _miHeaderThruline.Checked = false; _miHeaderUnderline.Checked = false; _miHeaderVertline.Checked = false; _miCellLines.Checked = false; + _miBottomline.Checked = false; tableView.Update (); } @@ -778,6 +799,13 @@ private void ToggleAlternatingColors () tableView.SetNeedsDisplay (); } + private void ToggleInvertSelectedCell () + { + //toggle menu item + _miInvert.Checked = !_miInvert.Checked; + tableView.Style.InvertSelectedCell = (bool)_miInvert.Checked; + tableView.SetNeedsDisplay (); + } private void ToggleInvertSelectedCellFirstCharacter () { //toggle menu item diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index 11357826ab..5239895607 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -556,12 +556,13 @@ public void TableView_ExpandLastColumn_True () } [Fact, AutoInitShutdown] - public void TableView_ExpandLastColumn_False () + public void TableView_ExpandLastColumn_False_AddEmptyColumn_False () { var tv = SetUpMiniTable (); // the thing we are testing tv.Style.ExpandLastColumn = false; + tv.Style.AddEmptyColumn = false; tv.Draw (); @@ -571,13 +572,29 @@ public void TableView_ExpandLastColumn_False () ├─┼─┤ │1│2│ "; -// TODO: Which one should be the correct behavior? - /*string expected = @" + TestHelpers.AssertDriverContentsAre (expected, output); + + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + } + + [Fact, AutoInitShutdown] + public void TableView_ExpandLastColumn_False_AddEmptyColumn_True () + { + var tv = SetUpMiniTable (); + + // the thing we are testing + tv.Style.ExpandLastColumn = false; + tv.Style.AddEmptyColumn = true; + + tv.Draw (); + + string expected = @" ┌─┬─┬────┐ │A│B│ │ ├─┼─┼────┤ │1│2│ │ -";*/ +"; TestHelpers.AssertDriverContentsAre (expected, output); // Shutdown must be called to safely clean up Application if Init has been called @@ -857,6 +874,137 @@ public void TableView_ColorTests_FocusedOrNot (bool focused) Application.Shutdown (); } + [Fact, AutoInitShutdown] + public void TableView_NullSymbol_Is_EmptyString () + { + var tv = new TableView () { + Width = 20, + Height = 4 + }; + + var dt = new DataTable (); + dt.Columns.Add ("C1"); + dt.Columns.Add ("C2"); + dt.Columns.Add ("C3"); + + dt.Rows.Add ("Hello", DBNull.Value, "f"); + + tv.Table = new DataTableSource (dt); + tv.Style.NullSymbol = string.Empty; + + Application.Top.Add (tv); + Application.Begin (Application.Top); + + tv.Draw (); + + var expected = +@" +┌─────┬──┬─────────┐ +│C1 │C2│C3 │ +├─────┼──┼─────────┤ +│Hello│ │f │ +"; + + TestHelpers.AssertDriverContentsAre (expected, output); + + Application.Shutdown (); + } + + [Theory, AutoInitShutdown] + [InlineData (false)] + [InlineData (true)] + public void TableView_ColorTests_InvertSelectedCell_and_InvertSelectedCellFirstCharacter (bool focused) + { + var tv = new TableView () { + Width = 20, + Height = 4 + }; + + var dt = new DataTable (); + dt.Columns.Add ("C1"); + dt.Columns.Add ("C2"); + dt.Columns.Add ("C3"); + + dt.Rows.Add ("Hello", DBNull.Value, "f"); + + tv.Table = new DataTableSource (dt); + tv.Style.NullSymbol = "-"; + + Application.Top.Add (tv); + Application.Begin (Application.Top); + + // private method for forcing the view to be focused/not focused + var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic); + + // when the view is/isn't focused + setFocusMethod.Invoke (tv, new object [] { focused, tv, true }); + + tv.Draw (); + + var expected = +@" +┌─────┬──┬─────────┐ +│C1 │C2│C3 │ +├─────┼──┼─────────┤ +│Hello│- │f │ +"; + + TestHelpers.AssertDriverContentsAre (expected, output); + + // Now the thing we really want to test are the styles + // InvertSelectedCell and InvertSelectedCellFirstCharacter! + + tv.FullRowSelect = true; + tv.Style.InvertSelectedCell = true; + + tv.Draw (); + string expectedColors = +@" +00000000000000000000 +00000000000000000000 +00000000000000000000 +02222011011111111110 +"; + var invertFocus = new Attribute (tv.ColorScheme.Focus.Background, tv.ColorScheme.Focus.Foreground); + var invertHotFocus = new Attribute (tv.ColorScheme.HotFocus.Background, tv.ColorScheme.HotFocus.Foreground); + var testColors = new Attribute [] { + // 0 + tv.ColorScheme.Normal, + // 1 + focused? tv.ColorScheme.Focus : tv.ColorScheme.HotFocus, + // 2 + focused? invertFocus : invertHotFocus}; + + TestHelpers.AssertDriverAttributesAre (expectedColors, driver: Application.Driver, testColors); + + tv.Style.InvertSelectedCell = false; + tv.Style.InvertSelectedCellFirstCharacter = true; + + tv.Draw (); + expectedColors = +@" +00000000000000000000 +00000000000000000000 +00000000000000000000 +02111011011111111110 +"; + TestHelpers.AssertDriverAttributesAre (expectedColors, driver: Application.Driver, testColors); + + tv.Style.ShowVerticalCellLines = false; + + tv.Draw (); + expectedColors = +@" +00000000000000000000 +00000000000000000000 +00000000000000000000 +12111111111111111111 +"; + TestHelpers.AssertDriverAttributesAre (expectedColors, driver: Application.Driver, testColors); + + Application.Shutdown (); + } + [Theory, AutoInitShutdown] [InlineData (false)] [InlineData (true)] @@ -947,7 +1095,7 @@ public void TableView_ColorsTest_RowColorGetter (bool focused) 00000 00000 00000 -21222 +01020 "; TestHelpers.AssertDriverAttributesAre (expectedColors, driver: Application.Driver, new Attribute [] { @@ -1965,19 +2113,24 @@ public void LongColumnTest () tableView.Draw (); expected = @" +│A │B │Very Long │ │ +├───┼───┼──────────┼────┤ +│1 │2 │aaaaaaaaaa│ │ +│1 │2 │aaa │ │ +"; + TestHelpers.AssertDriverContentsAre (expected, output); + + tableView.Style.AddEmptyColumn = false; + + tableView.LayoutSubviews (); + tableView.Draw (); + expected = +@" │A │B │Very Long │ ├───┼───┼──────────┤ │1 │2 │aaaaaaaaaa│ │1 │2 │aaa │ "; -// TODO: Which one should be the correct behavior? - /*expected = -@" -│A │B │Very Long │ │ -├───┼───┼──────────┼────┤ -│1 │2 │aaaaaaaaaa│ │ -│1 │2 │aaa │ │ -";*/ TestHelpers.AssertDriverContentsAre (expected, output); // MaxCellWidth limits MinCellWidth @@ -1988,10 +2141,10 @@ public void LongColumnTest () tableView.Draw (); expected = @" -│A │B │Very │ │ -├─────┼─────┼─────┼─────┤ -│1 │2 │aaaaa│ │ -│1 │2 │aaa │ │ +│A │B │Very │ +├─────┼─────┼─────┤ +│1 │2 │aaaaa│ +│1 │2 │aaa │ "; TestHelpers.AssertDriverContentsAre (expected, output); @@ -2260,7 +2413,7 @@ public void TestFullRowSelect_SelectionColorStopsAtTableEdge_WithCellLines () 0000000 0000000 0000000 -0111110 +0101010 0000000"; TestHelpers.AssertDriverAttributesAre (expected, driver: Application.Driver, normal, focus); From a1c98084c409545a0d530ee84d6a2e0ffd5ad114 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Mon, 29 Jan 2024 11:56:20 -0800 Subject: [PATCH 16/18] Use Runes for SeparatorSymbols and BackgroundSymbol --- Terminal.Gui/Views/TableView/TableStyle.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index 370ab368ce..c67fca2f27 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -120,12 +120,12 @@ public class TableStyle { /// /// The symbol to add after each header value to visually seperate values (if not using vertical gridlines) /// - public char HeaderSeparatorSymbol { get; set; } = ' '; + public Rune HeaderSeparatorSymbol { get; set; } = (Rune)' '; //CM.Glyphs.VLine; /// /// The symbol to add after each cell value to visually seperate values (if not using vertical gridlines) /// - public char SeparatorSymbol { get; set; } = ' '; + public Rune SeparatorSymbol { get; set; } = (Rune)' '; //CM.Glyphs.VLine; /// /// The text representation that should be rendered for cells with the value @@ -146,7 +146,7 @@ public class TableStyle { /// The symbol to pad outside table (if both and /// are False) /// - public char BackgroundSymbol { get; set; } = ' '; + public Rune BackgroundSymbol { get; set; } = (Rune)' '; /// /// Collection of columns for which you want special rendering (e.g. custom column lengths, text alignment etc) From b28d72e3375fb66dbefb3c85c1a03def5a2f5442 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Mon, 29 Jan 2024 12:00:33 -0800 Subject: [PATCH 17/18] Add glyph suggestion for SeparatorSymbols --- Terminal.Gui/Views/TableView/TableStyle.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index c67fca2f27..84db12bb00 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -119,13 +119,15 @@ public class TableStyle { /// /// The symbol to add after each header value to visually seperate values (if not using vertical gridlines) + /// CM.Glyphs.VLine can be used to emulate vertical grindlines that highlight with /// - public Rune HeaderSeparatorSymbol { get; set; } = (Rune)' '; //CM.Glyphs.VLine; + public Rune HeaderSeparatorSymbol { get; set; } = (Rune)' '; /// /// The symbol to add after each cell value to visually seperate values (if not using vertical gridlines) + /// CM.Glyphs.VLine can be used to emulate vertical grindlines that highlight with /// - public Rune SeparatorSymbol { get; set; } = (Rune)' '; //CM.Glyphs.VLine; + public Rune SeparatorSymbol { get; set; } = (Rune)' '; /// /// The text representation that should be rendered for cells with the value From d3c9a26d5ddd67ff8c664d94a02a36f81b1ec572 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Thu, 1 Feb 2024 12:50:26 -0800 Subject: [PATCH 18/18] Allow user to use HeaderThroughline with HeaderOverline and HeaderUnderline even though it's a strange choice --- Terminal.Gui/Views/TableView/TableView.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index b48544a086..c7364f9fb4 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -347,9 +347,7 @@ public override void OnDrawContent (Rect contentArea) Move (current.X, yh); - if (current.Width - colName.Length > 0 - && Style.ShowHorizontalHeaderThroughline - && (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { + if (current.Width > colName.Length && Style.ShowHorizontalHeaderThroughline) { if (colName.Sum (c => ((Rune)c).GetColumns ()) < current.Width) { Driver.AddStr (colName); @@ -462,8 +460,7 @@ private void RenderCellLines (int width, int height, ColumnToRender [] columnsTo row++; } if (Style.ShowHeaders) { - if (Style.ShowHorizontalHeaderThroughline && - (!Style.ShowHorizontalHeaderOverline || !Style.ShowHorizontalHeaderUnderline)) { + if (Style.ShowHorizontalHeaderThroughline) { grid.AddLine (new Point (0, row), width, Orientation.Horizontal, Style.InnerHeaderBorderStyle); } row++;