diff --git a/Examples/UICatalog/Scenarios/TableViewTest.cs b/Examples/UICatalog/Scenarios/TableViewTest.cs index 9654819cf3..ca78731b9e 100644 --- a/Examples/UICatalog/Scenarios/TableViewTest.cs +++ b/Examples/UICatalog/Scenarios/TableViewTest.cs @@ -89,6 +89,10 @@ public override void Main () ("ShowVerticalHeaderLines", () => tableView.Style.ShowVerticalHeaderLines, b => tableView.Style.ShowVerticalHeaderLines = b), ("ShowHorizontalHeaderUnderline", () => tableView.Style.ShowHorizontalHeaderUnderline, b => tableView.Style.ShowHorizontalHeaderUnderline = b), ("ShowVerticalCellLines", () => tableView.Style.ShowVerticalCellLines, b => tableView.Style.ShowVerticalCellLines = b), + ("ShowVerticalCellLineForFirstColumn", () => tableView.Style.ShowVerticalCellLineForFirstColumn, + b => tableView.Style.ShowVerticalCellLineForFirstColumn = b), + ("ShowVerticalCellLineForLastColumn", () => tableView.Style.ShowVerticalCellLineForLastColumn, + b => tableView.Style.ShowVerticalCellLineForLastColumn = b), ("InvertSelectedCellFirstCharacter", () => tableView.Style.InvertSelectedCellFirstCharacter, b => tableView.Style.InvertSelectedCellFirstCharacter = b), ("ShowHorizontalBottomline", () => tableView.Style.ShowHorizontalBottomLine, b => tableView.Style.ShowHorizontalBottomLine = b), diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs index 82ce7817d9..5850dcae31 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs @@ -207,8 +207,10 @@ internal FileDialog (IFileSystem? fileSystem) _tableViewContainer.Add (_tableView); _tableView.Style.ShowHorizontalHeaderOverline = false; - _tableView.Style.ShowVerticalCellLines = false; - _tableView.Style.ShowVerticalHeaderLines = false; + _tableView.Style.ShowVerticalCellLines = true; + _tableView.Style.ShowVerticalCellLineForFirstColumn = false; + _tableView.Style.ShowVerticalCellLineForLastColumn = false; + _tableView.Style.ShowVerticalHeaderLines = true; _tableView.Style.AlwaysShowHeaders = true; _tableView.Style.ShowHorizontalHeaderUnderline = false; _tableView.Style.ShowHorizontalBottomLine = false; diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs index cee6900dd0..cd193df22f 100644 --- a/Terminal.Gui/Views/TableView/TableStyle.cs +++ b/Terminal.Gui/Views/TableView/TableStyle.cs @@ -72,6 +72,18 @@ public class TableStyle /// True to render a solid line vertical line between cells public bool ShowVerticalCellLines { get; set; } = true; + /// + /// Gets or sets whether the left-most visible column renders a vertical line on its left side when vertical + /// lines are enabled. + /// + public bool ShowVerticalCellLineForFirstColumn { get; set; } = true; + + /// + /// Gets or sets whether the right-most visible column renders a vertical line on its right side when vertical + /// lines are enabled. + /// + public bool ShowVerticalCellLineForLastColumn { get; set; } = true; + /// True to render a solid line vertical line between headers public bool ShowVerticalHeaderLines { get; set; } = true; diff --git a/Terminal.Gui/Views/TableView/TableView.Content.cs b/Terminal.Gui/Views/TableView/TableView.Content.cs index a725c0b110..dec5623ea6 100644 --- a/Terminal.Gui/Views/TableView/TableView.Content.cs +++ b/Terminal.Gui/Views/TableView/TableView.Content.cs @@ -161,6 +161,8 @@ public void EnsureValidScrollOffsets () { int headerHeight = GetHeaderHeightIfAny (); int headerHeightVisible = CurrentHeaderHeightVisible (); + int leftOuterBorderWidth = ShouldRenderFirstOuterVerticalLine () ? 1 : 0; + int rightOuterBorderWidth = ShouldRenderLastOuterVerticalLine () ? 1 : 0; contentSize.Height += headerHeight + Table?.Rows ?? 0; if (Style.ShowHorizontalBottomLine) @@ -200,8 +202,7 @@ public void EnsureValidScrollOffsets () reservedFromIndex [i] = minWidths [i] + separator + reservedFromIndex [i + 1]; } - //right border - contentSize.Width += Style.ShowVerticalHeaderLines || Style.ShowVerticalCellLines ? 1 : 0; + contentSize.Width += leftOuterBorderWidth; var startRow = 0; int rowsToRender = Table.Rows; @@ -238,7 +239,7 @@ public void EnsureValidScrollOffsets () if (isVeryLast) { //remaining space for last column - int remainingSpace = Viewport.Width - contentSize.Width - (Style.ShowVerticalHeaderLines || Style.ShowVerticalCellLines ? 1 : 0); + int remainingSpace = Viewport.Width - contentSize.Width - rightOuterBorderWidth; if (Style.ExpandLastColumn && colWidth < remainingSpace) { @@ -250,8 +251,7 @@ public void EnsureValidScrollOffsets () // Reserve at least the header width for each subsequent visible column so that a wide // column does not consume all viewport space and push later columns off-screen. int reservedForRemaining = reservedFromIndex [columnIndex + 1]; - int borderWidth = Style.ShowVerticalHeaderLines || Style.ShowVerticalCellLines ? 1 : 0; - int availableForThisCol = Viewport.Width - contentSize.Width - reservedForRemaining - borderWidth - 1; // -1 for this column's separator + int availableForThisCol = Viewport.Width - contentSize.Width - reservedForRemaining - rightOuterBorderWidth - 1; // -1 for this column's separator // Don't shrink below this column's own minimum (header width or configured minimum) int thisColMin = minWidths [columnIndex]; @@ -275,8 +275,7 @@ public void EnsureValidScrollOffsets () columnIndex++; } - // for left border - contentSize.Width += Style.ShowVerticalHeaderLines || Style.ShowVerticalCellLines ? 1 : 0; + contentSize.Width += rightOuterBorderWidth; } else { @@ -349,4 +348,24 @@ private int MinimumWidthFor (int colIdx, ColumnStyle? colStyle) return min; } + + private bool ShouldRenderFirstOuterVerticalLine () + { + if (!Style.ShowVerticalCellLineForFirstColumn) + { + return false; + } + + return Style.ShowVerticalHeaderLines || Style.ShowVerticalCellLines; + } + + private bool ShouldRenderLastOuterVerticalLine () + { + if (!Style.ShowVerticalCellLineForLastColumn) + { + return false; + } + + return Style.ShowVerticalHeaderLines || Style.ShowVerticalCellLines; + } } diff --git a/Terminal.Gui/Views/TableView/TableView.Drawing.cs b/Terminal.Gui/Views/TableView/TableView.Drawing.cs index 38668fd975..d704a843a2 100644 --- a/Terminal.Gui/Views/TableView/TableView.Drawing.cs +++ b/Terminal.Gui/Views/TableView/TableView.Drawing.cs @@ -185,6 +185,9 @@ private void RenderBottomLine (int row, int availableWidth, ColumnToRender [] co { // Renders a line at the bottom of the table after all the data like: // └─────────────────────────────────┴──────────┴──────┴──────────┴────────┴────────────────────────────────────────────┘ + bool renderFirstOuterVerticalLine = Style.ShowVerticalCellLines && Style.ShowVerticalCellLineForFirstColumn; + bool renderLastOuterVerticalLine = Style.ShowVerticalCellLines && Style.ShowVerticalCellLineForLastColumn; + for (var c = 0; c < availableWidth; c++) { // Start by assuming we just draw a straight line the @@ -195,8 +198,10 @@ private void RenderBottomLine (int row, int availableWidth, ColumnToRender [] co { if (c == 0) { - // for first character render line - rune = Glyphs.LLCorner; + if (renderFirstOuterVerticalLine) + { + rune = Glyphs.LLCorner; + } } else if (columnsToRender.Any (r => r.X == c + 1)) { @@ -205,8 +210,10 @@ private void RenderBottomLine (int row, int availableWidth, ColumnToRender [] co } else if (c == availableWidth - 1) { - // for the last character in the table - rune = Glyphs.LRCorner; + if (renderLastOuterVerticalLine) + { + rune = Glyphs.LRCorner; + } } else if (!Style.ExpandLastColumn && columnsToRender.Any (r => r.IsVeryLast && r.X + r.Width - 1 == c)) { @@ -234,7 +241,7 @@ private void RenderHeaderMidline (int row, int availableWidth, ColumnToRender [] ClearLine (row, Viewport.Width); // render start of line - if (_style.ShowVerticalHeaderLines) + if (_style.ShowVerticalHeaderLines && Style.ShowVerticalCellLineForFirstColumn) { RenderRune (0, row, Glyphs.VLine); } @@ -259,7 +266,7 @@ private void RenderHeaderMidline (int row, int availableWidth, ColumnToRender [] Move (current.X - Viewport.X, row); AddStr (TruncateOrPad (colName, colName, current.Width, colStyle)); - if (!Style.ExpandLastColumn && current.IsVeryLast) + if (!Style.ExpandLastColumn && current.IsVeryLast && Style.ShowVerticalCellLineForLastColumn) { SetAttribute (GetAttributeForRole (VisualRole.Normal)); RenderSeparator (current.X + current.Width - 1, row, true); @@ -270,7 +277,7 @@ private void RenderHeaderMidline (int row, int availableWidth, ColumnToRender [] SetAttribute (GetAttributeForRole (VisualRole.Normal)); // render end of line - if (_style.ShowVerticalHeaderLines) + if (_style.ShowVerticalHeaderLines && Style.ShowVerticalCellLineForLastColumn) { RenderRune (availableWidth - 1, row, Glyphs.VLine); } @@ -280,6 +287,9 @@ private void RenderHeaderOverline (int row, int availableWidth, ColumnToRender [ { // Renders a line above table headers (when visible) like: // ┌────────────────────┬──────────┬───────────┬──────────────┬─────────┐ + bool renderFirstOuterVerticalLine = Style.ShowVerticalHeaderLines && Style.ShowVerticalCellLineForFirstColumn; + bool renderLastOuterVerticalLine = Style.ShowVerticalHeaderLines && Style.ShowVerticalCellLineForLastColumn; + for (var c = 0; c < availableWidth; c++) { Rune rune = Glyphs.HLine; @@ -288,7 +298,10 @@ private void RenderHeaderOverline (int row, int availableWidth, ColumnToRender [ { if (c == 0) { - rune = Glyphs.ULCorner; + if (renderFirstOuterVerticalLine) + { + rune = Glyphs.ULCorner; + } } // if the next column is the start of a header @@ -298,7 +311,10 @@ private void RenderHeaderOverline (int row, int availableWidth, ColumnToRender [ } else if (c == availableWidth - 1) { - rune = Glyphs.URCorner; + if (renderLastOuterVerticalLine) + { + rune = Glyphs.URCorner; + } } // if the next console column is the last column's end @@ -322,6 +338,9 @@ private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender */ // Renders a line below the table headers (when visible) like: // ├──────────┼───────────┼───────────────────┼──────────┼────────┼─────────────┤ + bool renderFirstOuterVerticalLine = Style.ShowVerticalHeaderLines && Style.ShowVerticalCellLineForFirstColumn; + bool renderLastOuterVerticalLine = Style.ShowVerticalHeaderLines && Style.ShowVerticalCellLineForLastColumn; + for (var c = 0; c < availableWidth; c++) { // Start by assuming we just draw a straight line the @@ -333,8 +352,10 @@ private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender { if (c == 0) { - // for first character render line - rune = Style.ShowVerticalCellLines ? Glyphs.LeftTee : Glyphs.LLCorner; + if (renderFirstOuterVerticalLine) + { + rune = Style.ShowVerticalCellLines ? Glyphs.LeftTee : Glyphs.LLCorner; + } } // if the next column is the start of a header @@ -344,8 +365,10 @@ private void RenderHeaderUnderline (int row, int availableWidth, ColumnToRender } else if (c == availableWidth - 1) { - // for the last character in the table - rune = Style.ShowVerticalCellLines ? Glyphs.RightTee : Glyphs.LRCorner; + if (renderLastOuterVerticalLine) + { + rune = Style.ShowVerticalCellLines ? Glyphs.RightTee : Glyphs.LRCorner; + } } // if the next console column is the last column's end @@ -446,7 +469,7 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen RenderSeparator (current.X - 1, row, false); - if (!Style.ExpandLastColumn && current.IsVeryLast) + if (!Style.ExpandLastColumn && current.IsVeryLast && Style.ShowVerticalCellLineForLastColumn) { RenderSeparator (current.X + current.Width - 1, row, false); } @@ -477,11 +500,14 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen SetAttribute (rowScheme.Normal); // render start and end of line - RenderRune (0, row, Glyphs.VLine); + if (Style.ShowVerticalCellLineForFirstColumn) + { + RenderRune (0, row, Glyphs.VLine); + } ColumnToRender? lastCol = columnsToRender.LastOrDefault (); - if (lastCol != null) + if (lastCol != null && Style.ShowVerticalCellLineForLastColumn) { RenderRune (lastCol.X + lastCol.Width - 1, row, Glyphs.VLine); } diff --git a/Tests/UnitTestsParallelizable/Views/FileDialogResultTests.cs b/Tests/UnitTestsParallelizable/Views/FileDialogResultTests.cs index acb1d037b2..69befbdf15 100644 --- a/Tests/UnitTestsParallelizable/Views/FileDialogResultTests.cs +++ b/Tests/UnitTestsParallelizable/Views/FileDialogResultTests.cs @@ -1,5 +1,6 @@ // Copilot +using System.Reflection; using System.IO.Abstractions.TestingHelpers; namespace UnitTests.Views; @@ -153,6 +154,22 @@ public void FileDialog_Cancel_ClearsResult_WhenPreviouslyAccepted () Assert.True (sd.Canceled); } + [Fact] + public void OpenDialog_UsesInnerTableSeparatorsWithoutOuterBorders () + { + using OpenDialog od = new TestableOpenDialog (); + + FieldInfo? tableViewField = typeof (FileDialog).GetField ("_tableView", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull (tableViewField); + + TableView tableView = Assert.IsType (tableViewField!.GetValue (od)); + + Assert.True (tableView.Style.ShowVerticalCellLines); + Assert.True (tableView.Style.ShowVerticalHeaderLines); + Assert.False (tableView.Style.ShowVerticalCellLineForFirstColumn); + Assert.False (tableView.Style.ShowVerticalCellLineForLastColumn); + } + /// Testable subclass that exposes the internal file-system constructor. private sealed class TestableFileDialog : FileDialog { diff --git a/Tests/UnitTestsParallelizable/Views/TableViewTests.cs b/Tests/UnitTestsParallelizable/Views/TableViewTests.cs index 504ba06b98..ef23713055 100644 --- a/Tests/UnitTestsParallelizable/Views/TableViewTests.cs +++ b/Tests/UnitTestsParallelizable/Views/TableViewTests.cs @@ -1190,6 +1190,58 @@ public void HeaderSeparatorLines_DoNotUseFocusAttribute_WhenTableHasFocus () tableView.Dispose (); } + // Copilot + [Fact] + public void ShowVerticalCellLines_CanHideOuterBorders_AndPreserveInnerSeparators () + { + IDriver driver = CreateTestDriver (20, 5); + + TableView tableView = new () { Driver = driver }; + tableView.BeginInit (); + tableView.EndInit (); + tableView.SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base); + tableView.Viewport = new Rectangle (0, 0, 20, 5); + + tableView.Style.ShowHeaders = true; + tableView.Style.ShowHorizontalHeaderUnderline = false; + tableView.Style.ShowHorizontalHeaderOverline = false; + tableView.Style.AlwaysShowHeaders = true; + tableView.Style.ShowVerticalCellLines = true; + tableView.Style.ShowVerticalHeaderLines = true; + tableView.Style.ExpandLastColumn = false; + tableView.Style.ShowVerticalCellLineForFirstColumn = false; + tableView.Style.ShowVerticalCellLineForLastColumn = false; + + DataTable dt = new (); + dt.Columns.Add ("Name"); + dt.Columns.Add ("Value"); + dt.Rows.Add ("A", "B"); + tableView.Table = new DataTableSource (dt); + + tableView.Layout (); + tableView.SetClipToScreen (); + tableView.Draw (); + + Cell [,] contents = driver.Contents!; + TableView.ColumnToRender [] columns = GetColumnsToRender (tableView); + + Assert.Equal (2, columns.Length); + + int leftBorderCol = 0; + int innerSeparatorCol = columns [1].X - 1; + int rightBorderCol = columns [1].X + columns [1].Width - 1; + + Assert.NotEqual (Glyphs.VLine.ToString (), contents [0, leftBorderCol].Grapheme); + Assert.Equal (Glyphs.VLine.ToString (), contents [0, innerSeparatorCol].Grapheme); + Assert.NotEqual (Glyphs.VLine.ToString (), contents [0, rightBorderCol].Grapheme); + + Assert.NotEqual (Glyphs.VLine.ToString (), contents [1, leftBorderCol].Grapheme); + Assert.Equal (Glyphs.VLine.ToString (), contents [1, innerSeparatorCol].Grapheme); + Assert.NotEqual (Glyphs.VLine.ToString (), contents [1, rightBorderCol].Grapheme); + + tableView.Dispose (); + } + // Claude - Opus 4.7 // Regression test for https://github.com/gui-cs/Terminal.Gui/issues/5126 // Clicking outside the row area of a TableView (e.g. below the last row) must @@ -1220,6 +1272,46 @@ public void Click_OutsideRows_DoesNotRaise_Activating () tableView.Dispose (); } + // Copilot + [Fact] + public void CalculateContentSize_HidingOuterVerticalCellLines_ReclaimsBothOuterColumns () + { + DataTable dt = new (); + dt.Columns.Add ("Name"); + dt.Columns.Add ("Value"); + dt.Rows.Add ("A", "B"); + + using TableView tableViewWithOuterBorders = new () + { + Table = new DataTableSource (dt), + Viewport = new Rectangle (0, 0, 20, 5) + }; + tableViewWithOuterBorders.BeginInit (); + tableViewWithOuterBorders.EndInit (); + tableViewWithOuterBorders.Style.ShowHeaders = true; + tableViewWithOuterBorders.Style.ShowVerticalCellLines = true; + tableViewWithOuterBorders.Style.ShowVerticalHeaderLines = true; + tableViewWithOuterBorders.Style.ExpandLastColumn = false; + tableViewWithOuterBorders.RefreshContentSize (); + + using TableView tableViewWithoutOuterBorders = new () + { + Table = new DataTableSource (dt), + Viewport = new Rectangle (0, 0, 20, 5) + }; + tableViewWithoutOuterBorders.BeginInit (); + tableViewWithoutOuterBorders.EndInit (); + tableViewWithoutOuterBorders.Style.ShowHeaders = true; + tableViewWithoutOuterBorders.Style.ShowVerticalCellLines = true; + tableViewWithoutOuterBorders.Style.ShowVerticalHeaderLines = true; + tableViewWithoutOuterBorders.Style.ExpandLastColumn = false; + tableViewWithoutOuterBorders.Style.ShowVerticalCellLineForFirstColumn = false; + tableViewWithoutOuterBorders.Style.ShowVerticalCellLineForLastColumn = false; + tableViewWithoutOuterBorders.RefreshContentSize (); + + Assert.Equal (tableViewWithOuterBorders.GetContentSize ().Width - 2, tableViewWithoutOuterBorders.GetContentSize ().Width); + } + // Claude - Opus 4.7 // Companion to Click_OutsideRows_DoesNotRaise_Activating: clicking on a real // cell still raises Activating as before.