TableView - adds last column dividing line (#1289)

* Adds a final vertical column line at the end of the last header in TableView

* Added clear line as first step in RenderRow

* Added TableStyle.EnforceMaxWidthOnLastColumn

* Added Scenario toggle setting and tests

* Fixed EnforceMaxWidthOnLastColumn when Bounds match exactly the last column width

* Fixed whitespace and comment on ColumntoRender.Width

* Renamed EnforceMaxWidthOnLastColumn to ExpandLastColumn
This commit is contained in:
Thomas Nind
2021-05-11 04:58:15 +01:00
committed by GitHub
parent 93d76b1a64
commit 0dd0f2e5c4
3 changed files with 158 additions and 33 deletions

View File

@@ -300,10 +300,15 @@ namespace Terminal.Gui {
// 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;
}
}
AddRuneAt(Driver,c,row,rune);
@@ -324,45 +329,26 @@ namespace Terminal.Gui {
for(int i =0 ; i<columnsToRender.Length;i++) {
var current = columnsToRender[i];
var availableWidthForCell = GetCellWidth(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,availableWidthForCell ,colStyle));
Driver.AddStr(TruncateOrPad(colName,colName,current.Width ,colStyle));
if (Style.ExpandLastColumn == false && current.IsVeryLast) {
RenderSeparator (current.X + current.Width-1, row, true);
}
}
//render end of line
if(style.ShowVerticalHeaderLines)
if (style.ShowVerticalHeaderLines)
AddRune(Bounds.Width-1,row,Driver.VLine);
}
/// <summary>
/// Calculates how much space is available to render index <paramref name="i"/> of the <paramref name="columnsToRender"/> given the remaining horizontal space
/// </summary>
/// <param name="columnsToRender"></param>
/// <param name="i"></param>
private int GetCellWidth (ColumnToRender [] columnsToRender, int i)
{
var current = columnsToRender[i];
var next = i+1 < columnsToRender.Length ? columnsToRender[i+1] : null;
if(next == null) {
// cell can fill to end of the line
return Bounds.Width - current.X;
}
else {
// cell can fill up to next cell start
return next.X - current.X;
}
}
private void RenderHeaderUnderline(int row,int availableWidth, ColumnToRender[] columnsToRender)
{
// Renders a line below the table headers (when visible) like:
@@ -385,6 +371,11 @@ namespace Terminal.Gui {
else if(c == availableWidth -1){
rune = Style.ShowVerticalCellLines ? Driver.RightTee : 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 = Style.ShowVerticalCellLines ? '┼' : Driver.BottomTee;
}
}
AddRuneAt(Driver,c,row,rune);
@@ -397,11 +388,15 @@ namespace Terminal.Gui {
if(style.ShowVerticalCellLines)
AddRune(0,row,Driver.VLine);
//start by clearing the entire line
Move (0,row);
Driver.SetAttribute (FullRowSelect && IsSelected(0,rowToRender) ? ColorScheme.HotFocus : ColorScheme.Normal);
Driver.AddStr (new string(' ',Bounds.Width));
// Render cells for each visible header for the current row
for(int i=0;i< columnsToRender.Length ;i++) {
var current = columnsToRender[i];
var availableWidthForCell = GetCellWidth(columnsToRender,i);
var colStyle = Style.GetColumnStyleIfAny(current.Column);
@@ -418,13 +413,17 @@ namespace Terminal.Gui {
// Render the (possibly truncated) cell value
var representation = GetRepresentation(val,colStyle);
Driver.AddStr (TruncateOrPad(val,representation,availableWidthForCell,colStyle));
Driver.AddStr (TruncateOrPad(val,representation, current.Width, colStyle));
// 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 (ColorScheme.Normal);
RenderSeparator(current.X-1,row,false);
if (Style.ExpandLastColumn == false && current.IsVeryLast) {
RenderSeparator (current.X + current.Width-1, row, false);
}
}
//render end of line
@@ -1019,21 +1018,26 @@ namespace Terminal.Gui {
rowsToRender -= GetHeaderHeight();
bool first = true;
var lastColumn = Table.Columns.Cast<DataColumn> ().Last ();
foreach (var col in Table.Columns.Cast<DataColumn>().Skip (ColumnOffset)) {
int startingIdxForCurrentHeader = usedSpace;
var colStyle = Style.GetColumnStyleIfAny(col);
int colWidth;
// is there enough space for this column (and it's data)?
usedSpace += CalculateMaxCellWidth (col, rowsToRender,colStyle) + padding;
usedSpace += colWidth = CalculateMaxCellWidth (col, rowsToRender,colStyle) + padding;
// no (don't render it) unless its the only column we are render (that must be one massively wide column!)
if (!first && usedSpace > availableHorizontalSpace)
yield break;
// there is space
yield return new ColumnToRender(col, startingIdxForCurrentHeader);
yield return new ColumnToRender(col, startingIdxForCurrentHeader,
// required for if we end up here because first == true i.e. we have a single massive width (overspilling bounds) column to present
Math.Min(availableHorizontalSpace,colWidth),
lastColumn == col);
first=false;
}
}
@@ -1215,6 +1219,17 @@ namespace Terminal.Gui {
/// </summary>
public Dictionary<DataColumn, ColumnStyle> ColumnStyles { get; set; } = new Dictionary<DataColumn, ColumnStyle> ();
/// <summary>
/// Determines rendering when the last column in the table is visible but it's
/// content or <see cref="ColumnStyle.MaxWidth"/> 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.
/// </summary>
/// <value></value>
public bool ExpandLastColumn {get;set;} = true;
/// <summary>
/// Returns the entry from <see cref="ColumnStyles"/> for the given <paramref name="col"/> or null if no custom styling is defined for it
/// </summary>
@@ -1254,11 +1269,25 @@ namespace Terminal.Gui {
/// </summary>
public int X { get; set; }
public ColumnToRender (DataColumn col, int x)
/// <summary>
/// The width that the column should occupy as calculated by <see cref="CalculateViewport(Rect, int)"/>. Note that this includes
/// space for padding i.e. the separator between columns.
/// </summary>
public int Width { get; }
/// <summary>
/// True if this column is the very last column in the <see cref="Table"/> (not just the last visible column)
/// </summary>
public bool IsVeryLast { get; }
public ColumnToRender (DataColumn col, int x, int width, bool isVeryLast)
{
Column = col;
X = x;
Width = width;
IsVeryLast = isVeryLast;
}
}
/// <summary>

View File

@@ -22,6 +22,7 @@ namespace UICatalog.Scenarios {
private MenuItem miHeaderUnderline;
private MenuItem miCellLines;
private MenuItem miFullRowSelect;
private MenuItem miExpandLastColumn;
public override void Setup ()
{
@@ -51,6 +52,7 @@ namespace UICatalog.Scenarios {
miHeaderUnderline =new MenuItem ("_HeaderUnderLine", "", () => ToggleUnderline()){Checked = tableView.Style.ShowHorizontalHeaderUnderline, 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 },
miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn()){Checked = tableView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked },
new MenuItem ("_AllLines", "", () => ToggleAllCellLines()),
new MenuItem ("_NoLines", "", () => ToggleNoCellLines()),
new MenuItem ("_ClearColumnStyles", "", () => ClearColumnStyles()),
@@ -181,6 +183,15 @@ namespace UICatalog.Scenarios {
tableView.FullRowSelect= miFullRowSelect.Checked;
tableView.Update();
}
private void ToggleExpandLastColumn()
{
miExpandLastColumn.Checked = !miExpandLastColumn.Checked;
tableView.Style.ExpandLastColumn = miExpandLastColumn.Checked;
tableView.Update();
}
private void ToggleCellLines()
{
miCellLines.Checked = !miCellLines.Checked;

View File

@@ -418,7 +418,92 @@ namespace Terminal.Gui.Views {
Assert.Equal(new Point(8,3),selected[5]);
}
/// <summary>
[Fact]
public void TableView_ExpandLastColumn_True()
{
var tv = SetUpMiniTable();
// the thing we are testing
tv.Style.ExpandLastColumn = true;
tv.Redraw(tv.Bounds);
string expected = @"
┌─┬──────┐
│A│B │
├─┼──────┤
│1│2 │
";
GraphViewTests.AssertDriverContentsAre(expected);
}
[Fact]
public void TableView_ExpandLastColumn_False()
{
var tv = SetUpMiniTable();
// the thing we are testing
tv.Style.ExpandLastColumn = false;
tv.Redraw(tv.Bounds);
string expected = @"
┌─┬─┬────┐
│A│B│ │
├─┼─┼────┤
│1│2│ │
";
GraphViewTests.AssertDriverContentsAre(expected);
}
[Fact]
public void TableView_ExpandLastColumn_False_ExactBounds()
{
var tv = SetUpMiniTable();
// the thing we are testing
tv.Style.ExpandLastColumn = false;
// width exactly matches the max col widths
tv.Bounds = new Rect(0,0,5,4);
tv.Redraw(tv.Bounds);
string expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│2│
";
GraphViewTests.AssertDriverContentsAre(expected);
}
private TableView SetUpMiniTable ()
{
var tv = new TableView();
tv.Bounds = new Rect(0,0,10,4);
var dt = new DataTable();
var colA = dt.Columns.Add("A");
var colB = dt.Columns.Add("B");
dt.Rows.Add(1,2);
tv.Table = dt;
tv.Style.GetOrCreateColumnStyle(colA).MinWidth=1;
tv.Style.GetOrCreateColumnStyle(colA).MinWidth=1;
tv.Style.GetOrCreateColumnStyle(colB).MaxWidth=1;
tv.Style.GetOrCreateColumnStyle(colB).MaxWidth=1;
GraphViewTests.InitFakeDriver();
tv.ColorScheme = new ColorScheme(){
Normal = Application.Driver.MakeAttribute(Color.White,Color.Black),
HotFocus = Application.Driver.MakeAttribute(Color.White,Color.Black)
};
return tv;
}
/// <summary>
/// Builds a simple table of string columns with the requested number of columns and rows
/// </summary>
/// <param name="cols"></param>