mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Added right and left scrolling feature to the ListView.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user