diff --git a/ReactiveExample/ReactiveExample.csproj b/ReactiveExample/ReactiveExample.csproj
index 0241ee623..c461f7f67 100644
--- a/ReactiveExample/ReactiveExample.csproj
+++ b/ReactiveExample/ReactiveExample.csproj
@@ -7,7 +7,7 @@
-
-
+
+
diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs
index 33ca8f0eb..f0d59ae81 100644
--- a/Terminal.Gui/Core/Application.cs
+++ b/Terminal.Gui/Core/Application.cs
@@ -110,6 +110,15 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// Alternative key to navigate forwards through all views. Ctrl+Tab is always used.
+ ///
+ public static Key AlternateForwardKey { get; set; } = Key.PageDown | Key.CtrlMask;
+ ///
+ /// Alternative key to navigate backwards through all views. Shift+Ctrl+Tab is always used.
+ ///
+ public static Key AlternateBackwardKey { get; set; } = Key.PageUp | Key.CtrlMask;
+
///
/// The driver for the application
///
diff --git a/Terminal.Gui/Core/PosDim.cs b/Terminal.Gui/Core/PosDim.cs
index 40abbb6ca..3334838ca 100644
--- a/Terminal.Gui/Core/PosDim.cs
+++ b/Terminal.Gui/Core/PosDim.cs
@@ -242,13 +242,12 @@ namespace Terminal.Gui {
/// The that is the sum of the values of left and right.
public static Pos operator + (Pos left, Pos right)
{
- PosCombine newPos = new PosCombine (true, left, right);
- if (posCombine?.ToString () != newPos.ToString ()) {
- var view = left as PosView;
- if (view != null) {
- view.Target.SetNeedsLayout ();
- }
+ if (left is PosAbsolute && right is PosAbsolute) {
+ posCombine = null;
+ return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
}
+ PosCombine newPos = new PosCombine (true, left, right);
+ SetPosCombine (left, newPos);
return posCombine = newPos;
}
@@ -260,13 +259,23 @@ namespace Terminal.Gui {
/// The that is the left minus right.
public static Pos operator - (Pos left, Pos right)
{
+ if (left is PosAbsolute && right is PosAbsolute) {
+ posCombine = null;
+ return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
+ }
PosCombine newPos = new PosCombine (false, left, right);
+ SetPosCombine (left, newPos);
+ return posCombine = newPos;
+ }
+
+ static void SetPosCombine (Pos left, PosCombine newPos)
+ {
if (posCombine?.ToString () != newPos.ToString ()) {
var view = left as PosView;
- if (view != null)
+ if (view != null) {
view.Target.SetNeedsLayout ();
+ }
}
- return posCombine = newPos;
}
internal class PosView : Pos {
@@ -526,6 +535,8 @@ namespace Terminal.Gui {
}
+ static DimCombine dimCombine;
+
///
/// Adds a to a , yielding a new .
///
@@ -534,7 +545,13 @@ namespace Terminal.Gui {
/// The that is the sum of the values of left and right.
public static Dim operator + (Dim left, Dim right)
{
- return new DimCombine (true, left, right);
+ if (left is DimAbsolute && right is DimAbsolute) {
+ dimCombine = null;
+ return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
+ }
+ DimCombine newDim = new DimCombine (true, left, right);
+ SetDimCombine (left, newDim);
+ return dimCombine = newDim;
}
///
@@ -545,7 +562,23 @@ namespace Terminal.Gui {
/// The that is the left minus right.
public static Dim operator - (Dim left, Dim right)
{
- return new DimCombine (false, left, right);
+ if (left is DimAbsolute && right is DimAbsolute) {
+ dimCombine = null;
+ return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
+ }
+ DimCombine newDim = new DimCombine (false, left, right);
+ SetDimCombine (left, newDim);
+ return dimCombine = newDim;
+ }
+
+ static void SetDimCombine (Dim left, DimCombine newPos)
+ {
+ if (dimCombine?.ToString () != newPos.ToString ()) {
+ var view = left as DimView;
+ if (view != null) {
+ view.Target.SetNeedsLayout ();
+ }
+ }
}
internal class DimView : Dim {
diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs
index 69f75e3a3..a933f9a05 100644
--- a/Terminal.Gui/Core/Toplevel.cs
+++ b/Terminal.Gui/Core/Toplevel.cs
@@ -238,10 +238,18 @@ namespace Terminal.Gui {
}
return true;
case Key.Tab | Key.CtrlMask:
+ case Key key when key == Application.AlternateForwardKey: // Needed on Unix
Application.Top.FocusNext ();
+ if (Application.Top.Focused == null) {
+ Application.Top.FocusNext ();
+ }
return true;
case Key.Tab | Key.ShiftMask | Key.CtrlMask:
+ case Key key when key == Application.AlternateBackwardKey: // Needed on Unix
Application.Top.FocusPrev ();
+ if (Application.Top.Focused == null) {
+ Application.Top.FocusPrev ();
+ }
return true;
case Key.L | Key.CtrlMask:
Application.Refresh ();
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index 195fdd700..c213080a2 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -8,7 +8,7 @@
-
+
all
diff --git a/Terminal.Gui/Views/TableView.cs b/Terminal.Gui/Views/TableView.cs
index c91a2942a..e8e15fe07 100644
--- a/Terminal.Gui/Views/TableView.cs
+++ b/Terminal.Gui/Views/TableView.cs
@@ -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
- /// Calculates how much space is available to render index of the given the remaining horizontal space
- ///
- ///
- ///
- 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 ().Last ();
foreach (var col in Table.Columns.Cast().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 {
///
public Dictionary ColumnStyles { get; set; } = new Dictionary ();
+
+ ///
+ /// Determines rendering when the last column in the table is visible but it's
+ /// 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.
+ ///
+ ///
+ public bool ExpandLastColumn {get;set;} = true;
+
///
/// Returns the entry from for the given or null if no custom styling is defined for it
///
@@ -1254,11 +1269,25 @@ namespace Terminal.Gui {
///
public int X { get; set; }
- public ColumnToRender (DataColumn col, int x)
+ ///
+ /// The width that the column should occupy as calculated by . Note that this includes
+ /// space for padding i.e. the separator between columns.
+ ///
+ public int Width { get; }
+
+ ///
+ /// True if this column is the very last column in the (not just the last visible column)
+ ///
+ public bool IsVeryLast { get; }
+
+ public ColumnToRender (DataColumn col, int x, int width, bool isVeryLast)
{
Column = col;
X = x;
+ Width = width;
+ IsVeryLast = isVeryLast;
}
+
}
///
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index 9717ad193..62d583c42 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -2017,7 +2017,7 @@ namespace Terminal.Gui {
StopSelecting ();
}
int nPageDnShift = Frame.Height - 1;
- if (currentRow > 0 && currentRow < model.Count) {
+ if (currentRow >= 0 && currentRow < model.Count) {
if (columnTrack == -1)
columnTrack = currentColumn;
currentRow = (currentRow + nPageDnShift) > model.Count
diff --git a/UICatalog/Scenarios/TableEditor.cs b/UICatalog/Scenarios/TableEditor.cs
index 483010a2b..229cc7c71 100644
--- a/UICatalog/Scenarios/TableEditor.cs
+++ b/UICatalog/Scenarios/TableEditor.cs
@@ -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;
diff --git a/UnitTests/ApplicationTests.cs b/UnitTests/ApplicationTests.cs
index a7f5f597e..f35eaea51 100644
--- a/UnitTests/ApplicationTests.cs
+++ b/UnitTests/ApplicationTests.cs
@@ -4,7 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Xunit;
-// Alais Console to MockConsole so we don't accidentally use Console
+// Alias Console to MockConsole so we don't accidentally use Console
using Console = Terminal.Gui.FakeConsole;
namespace Terminal.Gui.Core {
@@ -19,7 +19,7 @@ namespace Terminal.Gui.Core {
[Fact]
public void Init_Shutdown_Cleans_Up ()
{
- // Verify inital state is per spec
+ // Verify initial state is per spec
Pre_Init_State ();
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
@@ -175,7 +175,7 @@ namespace Terminal.Gui.Core {
// Setup Mock driver
Init ();
- // Setup some fake kepresses (This)
+ // Setup some fake keypresses (This)
var input = "Tests";
// Put a control-q in at the end
@@ -268,5 +268,106 @@ namespace Terminal.Gui.Core {
Application.Shutdown ();
Assert.Null (SynchronizationContext.Current);
}
+
+ [Fact]
+ public void AlternateForwardKey_AlternateBackwardKey_Tests ()
+ {
+ Init ();
+
+ var top = Application.Top;
+ var w1 = new Window ();
+ var v1 = new TextField ();
+ var v2 = new TextView ();
+ w1.Add (v1, v2);
+
+ var w2 = new Window ();
+ var v3 = new CheckBox ();
+ var v4 = new Button ();
+ w2.Add (v3, v4);
+
+ top.Add (w1, w2);
+
+ Application.Iteration += () => {
+ Assert.True (v1.HasFocus);
+ // Using default keys.
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v2.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v3.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v4.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v1.HasFocus);
+
+ top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab,
+ new KeyModifiers () { Shift = true, Ctrl = true }));
+ Assert.True (v4.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab,
+ new KeyModifiers () { Shift = true, Ctrl = true }));
+ Assert.True (v3.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab,
+ new KeyModifiers () { Shift = true, Ctrl = true }));
+ Assert.True (v2.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.ShiftMask | Key.CtrlMask | Key.Tab,
+ new KeyModifiers () { Shift = true, Ctrl = true }));
+ Assert.True (v1.HasFocus);
+
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v2.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v3.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v4.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageDown,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v1.HasFocus);
+
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v4.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v3.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v2.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.PageUp,
+ new KeyModifiers () { Ctrl = true }));
+ Assert.True (v1.HasFocus);
+
+ // Using another's alternate keys.
+ Application.AlternateForwardKey = Key.F7;
+ Application.AlternateBackwardKey = Key.F6;
+
+ top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ()));
+ Assert.True (v2.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ()));
+ Assert.True (v3.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ()));
+ Assert.True (v4.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.F7, new KeyModifiers ()));
+ Assert.True (v1.HasFocus);
+
+ top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ()));
+ Assert.True (v4.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ()));
+ Assert.True (v3.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ()));
+ Assert.True (v2.HasFocus);
+ top.ProcessKey (new KeyEvent (Key.F6, new KeyModifiers ()));
+ Assert.True (v1.HasFocus);
+
+ Application.RequestStop ();
+ };
+
+ Application.Run (top);
+ }
}
}
diff --git a/UnitTests/DimTests.cs b/UnitTests/DimTests.cs
index 074b5425c..d961b02b0 100644
--- a/UnitTests/DimTests.cs
+++ b/UnitTests/DimTests.cs
@@ -590,5 +590,109 @@ namespace Terminal.Gui.Core {
Assert.Throws (() => Application.Run ());
Application.Shutdown ();
}
+
+
+ [Fact]
+ public void Dim_Add_Operator ()
+ {
+
+ Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+ var top = Application.Top;
+
+ var view = new View () { X = 0, Y = 0, Width = 20, Height = 0 };
+ var field = new TextField () { X = 0, Y = Pos.Bottom (view), Width = 20 };
+ var count = 0;
+
+ field.KeyDown += (k) => {
+ if (k.KeyEvent.Key == Key.Enter) {
+ field.Text = $"Label {count}";
+ var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 };
+ view.Add (label);
+ Assert.Equal ($"Label {count}", label.Text);
+ Assert.Equal ($"Pos.Absolute({count})", label.Y.ToString ());
+
+ Assert.Equal ($"Dim.Absolute({count})", view.Height.ToString ());
+ view.Height += 1;
+ count++;
+ Assert.Equal ($"Dim.Absolute({count})", view.Height.ToString ());
+ }
+ };
+
+ Application.Iteration += () => {
+ while (count < 20) {
+ field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
+ }
+
+ Application.RequestStop ();
+ };
+
+ var win = new Window ();
+ win.Add (view);
+ win.Add (field);
+
+ top.Add (win);
+
+ Application.Run (top);
+
+ Assert.Equal (20, count);
+ }
+
+ [Fact]
+ public void Dim_Subtract_Operator ()
+ {
+
+ Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+ var top = Application.Top;
+
+ var view = new View () { X = 0, Y = 0, Width = 20, Height = 0 };
+ var field = new TextField () { X = 0, Y = Pos.Bottom (view), Width = 20 };
+ var count = 20;
+ var listLabels = new List