Added right and left scrolling feature to the ListView.

This commit is contained in:
BDisp
2021-01-15 01:45:40 +00:00
parent bf6c4ec417
commit b88dbb672b
6 changed files with 226 additions and 34 deletions

View File

@@ -1342,8 +1342,8 @@ namespace Terminal.Gui {
// Draw the subview
// Use the view's bounds (view-relative; Location will always be (0,0)
if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
view.Redraw (view.Bounds);
view.OnDrawContent (view.Bounds);
view.Redraw (view.Bounds);
}
}
view.NeedDisplay = Rect.Empty;

View File

@@ -34,6 +34,11 @@ namespace Terminal.Gui {
/// </summary>
int Count { get; }
/// <summary>
/// Returns the maximum length of elements to display
/// </summary>
int Length { get; }
/// <summary>
/// This method is invoked to render a specified item, the method should cover the entire provided width.
/// </summary>
@@ -45,10 +50,11 @@ namespace Terminal.Gui {
/// <param name="col">The column where the rendering will start</param>
/// <param name="line">The line where the rendering will be done.</param>
/// <param name="width">The width that must be filled out.</param>
/// <param name="start">The index of the string to be displayed.</param>
/// <remarks>
/// The default color will be set before this method is invoked, and will be based on whether the item is selected or not.
/// </remarks>
void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width);
void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0);
/// <summary>
/// Should return whether the specified item is currently marked.
@@ -103,7 +109,7 @@ namespace Terminal.Gui {
/// </para>
/// </remarks>
public class ListView : View {
int top;
int top, left;
int selected;
IListDataSource source;
@@ -146,7 +152,7 @@ namespace Terminal.Gui {
/// </summary>
/// <value>An item implementing the IList interface.</value>
/// <remarks>
/// Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome rendering.
/// Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custom rendering.
/// </remarks>
public Task SetSourceAsync (IList source)
{
@@ -211,6 +217,28 @@ namespace Terminal.Gui {
}
}
/// <summary>
/// Gets or sets the left column where the item start to be displayed at on the <see cref="ListView"/>.
/// </summary>
/// <value>The left position.</value>
public int LeftItem {
get => left;
set {
if (source == null)
return;
if (left < 0 || top >= source.Count)
throw new ArgumentException ("value");
left = value;
SetNeedsDisplay ();
}
}
/// <summary>
/// Gets the widest item.
/// </summary>
public int Maxlength => (source?.Length) ?? 0;
/// <summary>
/// Gets or sets the index of the currently selected item.
/// </summary>
@@ -229,7 +257,6 @@ namespace Terminal.Gui {
}
}
static IListDataSource MakeWrapper (IList source)
{
return new ListWrapper (source);
@@ -301,6 +328,7 @@ namespace Terminal.Gui {
var item = top;
bool focused = HasFocus;
int col = allowsMarking ? 2 : 0;
int start = left;
for (int row = 0; row < f.Height; row++, item++) {
bool isSelected = item == selected;
@@ -320,7 +348,7 @@ namespace Terminal.Gui {
Driver.AddRune (source.IsMarked (item) ? (AllowsMultipleSelection ? Driver.Checked : Driver.Selected) : (AllowsMultipleSelection ? Driver.UnChecked : Driver.UnSelected));
Driver.AddRune (' ');
}
Source.Render (this, Driver, isSelected, item, col, row, f.Width - col);
Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start);
}
}
}
@@ -572,6 +600,26 @@ namespace Terminal.Gui {
SetNeedsDisplay ();
}
/// <summary>
/// Scrolls the view right.
/// </summary>
/// <param name="cols">Number of columns to scroll right.</param>
public virtual void ScrollRight (int cols)
{
left = Math.Max (Math.Min (left + cols, Maxlength - 1), 0);
SetNeedsDisplay ();
}
/// <summary>
/// Scrolls the view left.
/// </summary>
/// <param name="cols">Number of columns to scroll left.</param>
public virtual void ScrollLeft (int cols)
{
left = Math.Max (left - cols, 0);
SetNeedsDisplay ();
}
int lastSelectedItem = -1;
private bool allowsMultipleSelection = true;
@@ -639,7 +687,8 @@ namespace Terminal.Gui {
public override bool MouseEvent (MouseEvent me)
{
if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) &&
me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp)
me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp &&
me.Flags != MouseFlags.WheeledRight && me.Flags != MouseFlags.WheeledLeft)
return false;
if (!HasFocus && CanFocus) {
@@ -656,6 +705,12 @@ namespace Terminal.Gui {
} else if (me.Flags == MouseFlags.WheeledUp) {
ScrollUp (1);
return true;
} else if (me.Flags == MouseFlags.WheeledRight) {
ScrollRight (1);
return true;
} else if (me.Flags == MouseFlags.WheeledLeft) {
ScrollLeft (1);
return true;
}
if (me.Y + top >= source.Count) {
@@ -687,7 +742,7 @@ namespace Terminal.Gui {
public class ListWrapper : IListDataSource {
IList src;
BitArray marks;
int count;
int count, len;
/// <summary>
/// Initializes a new instance of <see cref="ListWrapper"/> given an <see cref="IList"/>
@@ -698,7 +753,8 @@ namespace Terminal.Gui {
if (source != null) {
count = source.Count;
marks = new BitArray (count);
this.src = source;
src = source;
len = GetMaxLengthItem ();
}
}
@@ -707,11 +763,42 @@ namespace Terminal.Gui {
/// </summary>
public int Count => src != null ? src.Count : 0;
void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
/// <summary>
/// Gets the maximum item length in the <see cref="IList"/>.
/// </summary>
public int Length => len;
int GetMaxLengthItem ()
{
if (src?.Count == 0) {
return 0;
}
int maxLength = 0;
for (int i = 0; i < src.Count; i++) {
var t = src [i];
int l;
if (t is ustring u) {
l = u.RuneCount;
} else if (t is string s) {
l = s.Length;
} else {
l = t.ToString ().Length;
}
if (l > maxLength) {
maxLength = l;
}
}
return maxLength;
}
void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0)
{
int byteLen = ustr.Length;
int used = 0;
for (int i = 0; i < byteLen;) {
for (int i = start; i < byteLen;) {
(var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen);
var count = Rune.ColumnWidth (rune);
if (used + count > width)
@@ -735,19 +822,21 @@ namespace Terminal.Gui {
/// <param name="col">The col where to move.</param>
/// <param name="line">The line where to move.</param>
/// <param name="width">The item width.</param>
public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width)
/// <param name="start">The index of the string to be displayed.</param>
public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width, int start = 0)
{
container.Move (col, line);
var t = src [item];
if (t == null) {
RenderUstr (driver, ustring.Make (""), col, line, width);
} else {
if (t is ustring) {
RenderUstr (driver, (ustring)t, col, line, width);
} else if (t is string) {
RenderUstr (driver, (string)t, col, line, width);
} else
RenderUstr (driver, t.ToString (), col, line, width);
if (t is ustring u) {
RenderUstr (driver, u, col, line, width, start);
} else if (t is string s) {
RenderUstr (driver, s, col, line, width, start);
} else {
RenderUstr (driver, t.ToString (), col, line, width, start);
}
}
}
@@ -793,14 +882,14 @@ namespace Terminal.Gui {
/// </summary>
public int Item { get; }
/// <summary>
/// The the <see cref="ListView"/> item.
/// The <see cref="ListView"/> item.
/// </summary>
public object Value { get; }
/// <summary>
/// Initializes a new instance of <see cref="ListViewItemEventArgs"/>
/// </summary>
/// <param name="item">The index of the the <see cref="ListView"/> item.</param>
/// <param name="item">The index of the <see cref="ListView"/> item.</param>
/// <param name="value">The <see cref="ListView"/> item</param>
public ListViewItemEventArgs (int item, object value)
{

View File

@@ -139,6 +139,8 @@ namespace Terminal.Gui {
} else {
position = Math.Max (position + max, 0);
}
} else if (max < 0) {
position = Math.Max (position + max, 0);
}
OnChangedPosition ();
SetNeedsDisplay ();
@@ -173,6 +175,7 @@ namespace Terminal.Gui {
Visible = true;
} else {
Visible = false;
Position = 0;
}
Width = vertical ? 1 : Dim.Width (Host);
Height = vertical ? Dim.Height (Host) : 1;

View File

@@ -25,7 +25,7 @@ namespace UICatalog {
Height = 1,
};
Win.Add (_customRenderCB);
_customRenderCB.Toggled += _customRenderCB_Toggled; ;
_customRenderCB.Toggled += _customRenderCB_Toggled;
_allowMarkingCB = new CheckBox ("Allow Marking") {
X = Pos.Right (_customRenderCB) + 1,
@@ -56,6 +56,9 @@ namespace UICatalog {
Win.Add (_listView);
var vertical = new ScrollBarView (_listView, true);
var horizontal = new ScrollBarView (_listView, false);
vertical.OtherScrollBarView = horizontal;
horizontal.OtherScrollBarView = vertical;
vertical.ChangedPosition += () => {
_listView.TopItem = vertical.Position;
@@ -65,13 +68,26 @@ namespace UICatalog {
_listView.SetNeedsDisplay ();
};
horizontal.ChangedPosition += () => {
_listView.LeftItem = horizontal.Position;
if (_listView.LeftItem != horizontal.Position) {
horizontal.Position = _listView.LeftItem;
}
_listView.SetNeedsDisplay ();
};
_listView.DrawContent += (e) => {
vertical.Size = _listView.Source.Count;
vertical.Size = _listView.Source.Count - 1;
vertical.Position = _listView.TopItem;
vertical.ColorScheme = _listView.ColorScheme;
horizontal.Size = _listView.Maxlength;
horizontal.Position = _listView.LeftItem;
vertical.ColorScheme = horizontal.ColorScheme = _listView.ColorScheme;
if (vertical.ShowScrollIndicator) {
vertical.Redraw (e);
}
if (horizontal.ShowScrollIndicator) {
horizontal.Redraw (e);
}
};
_listView.SetSource (_scenarios);
@@ -114,15 +130,16 @@ namespace UICatalog {
int _nameColumnWidth = 30;
private List<Type> scenarios;
BitArray marks;
int count;
int count, len;
public List<Type> Scenarios {
get => scenarios;
get => scenarios;
set {
if (value != null) {
count = value.Count;
marks = new BitArray (count);
scenarios = value;
len = GetMaxLengthItem ();
}
}
}
@@ -135,14 +152,16 @@ namespace UICatalog {
public int Count => Scenarios != null ? Scenarios.Count : 0;
public int Length => len;
public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width)
public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0)
{
container.Move (col, line);
// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width);
RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width, start);
}
public void SetMark (int item, bool value)
@@ -151,11 +170,30 @@ namespace UICatalog {
marks [item] = value;
}
int GetMaxLengthItem ()
{
if (scenarios?.Count == 0) {
return 0;
}
int maxLength = 0;
for (int i = 0; i < scenarios.Count; i++) {
var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [i]));
var sc = $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [i])}";
var l = sc.Length;
if (l > maxLength) {
maxLength = l;
}
}
return maxLength;
}
// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0)
{
int used = 0;
int index = 0;
int index = start;
while (index < ustr.Length) {
(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
var count = Rune.ColumnWidth (rune);

View File

@@ -39,6 +39,41 @@ namespace UICatalog.Scenarios {
listview.SelectedItemChanged += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
Win.Add (lbListView, listview);
var vertical = new ScrollBarView (listview, true);
var horizontal = new ScrollBarView (listview, false);
vertical.OtherScrollBarView = horizontal;
horizontal.OtherScrollBarView = vertical;
vertical.ChangedPosition += () => {
listview.TopItem = vertical.Position;
if (listview.TopItem != vertical.Position) {
vertical.Position = listview.TopItem;
}
listview.SetNeedsDisplay ();
};
horizontal.ChangedPosition += () => {
listview.LeftItem = horizontal.Position;
if (listview.LeftItem != horizontal.Position) {
horizontal.Position = listview.LeftItem;
}
listview.SetNeedsDisplay ();
};
listview.DrawContent += (e) => {
vertical.Size = listview.Source.Count - 1;
vertical.Position = listview.TopItem;
horizontal.Size = listview.Maxlength;
horizontal.Position = listview.LeftItem;
vertical.ColorScheme = horizontal.ColorScheme = listview.ColorScheme;
if (vertical.ShowScrollIndicator) {
vertical.Redraw (e);
}
if (horizontal.ShowScrollIndicator) {
horizontal.Redraw (e);
}
};
// ComboBox
var lbComboBox = new Label ("ComboBox") {
ColorScheme = Colors.TopLevel,

View File

@@ -498,31 +498,58 @@ namespace UICatalog {
}
internal class ScenarioListDataSource : IListDataSource {
private readonly int len;
public List<Type> Scenarios { get; set; }
public bool IsMarked (int item) => false;
public int Count => Scenarios.Count;
public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
public int Length => len;
public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width)
public ScenarioListDataSource (List<Type> itemList)
{
Scenarios = itemList;
len = GetMaxLengthItem ();
}
public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0)
{
container.Move (col, line);
// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width);
RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width, start);
}
public void SetMark (int item, bool value)
{
}
int GetMaxLengthItem ()
{
if (Scenarios?.Count == 0) {
return 0;
}
int maxLength = 0;
for (int i = 0; i < Scenarios.Count; i++) {
var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [i]));
var sc = $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [i])}";
var l = sc.Length;
if (l > maxLength) {
maxLength = l;
}
}
return maxLength;
}
// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0)
{
int used = 0;
int index = 0;
int index = start;
while (index < ustr.Length) {
(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
var count = Rune.ColumnWidth (rune);