瀏覽代碼

Added right and left scrolling feature to the ListView.

BDisp 4 年之前
父節點
當前提交
b88dbb672b

+ 1 - 1
Terminal.Gui/Core/View.cs

@@ -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;

+ 108 - 19
Terminal.Gui/Views/ListView.cs

@@ -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)
 		{

+ 3 - 0
Terminal.Gui/Views/ScrollBarView.cs

@@ -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;

+ 47 - 9
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -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);

+ 35 - 0
UICatalog/Scenarios/ListsAndCombos.cs

@@ -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,

+ 32 - 5
UICatalog/UICatalog.cs

@@ -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 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)
+			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);