Browse Source

ListView works

Miguel de Icaza 7 years ago
parent
commit
da8307dd03
4 changed files with 215 additions and 7 deletions
  1. 204 5
      Terminal.Gui/Views/ListView.cs
  2. 1 1
      Terminal.csproj
  3. 9 0
      demo.cs
  4. 1 1
      packages.config

+ 204 - 5
Terminal.Gui/Views/ListView.cs

@@ -4,33 +4,107 @@
 // Authors:
 // Authors:
 //   Miguel de Icaza ([email protected])
 //   Miguel de Icaza ([email protected])
 //
 //
+// 
+// TODO:
+//   - Should we support multiple columns, if so, how should that be done?
+//   - Show mark for items that have been marked.
+//   - Mouse support
+//   - Scrollbars?
+//
+// Column considerations:
+//   - Would need a way to specify widths
+//   - Should it automatically extract data out of structs/classes based on public fields/properties?
+//   - It seems that this would be useful just for the "simple" API, not the IListDAtaSource, as that one has full support for it.
+//   - Should a function be specified that retrieves the individual elements?   
+// 
 using System;
 using System;
 using System.Collections;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using NStack;
 using NStack;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
+	/// <summary>
+	/// Implement this interface to provide your own custom rendering for a list.
+	/// </summary>
 	public interface IListDataSource {
 	public interface IListDataSource {
+		/// <summary>
+		/// Returns the number of elements to display
+		/// </summary>
 		int Count { get; }
 		int Count { get; }
-		void Render (int item, int col, int line, int width);
+
+		/// <summary>
+		/// This method is invoked to render a specified item, the method should cover the entire provided width.
+		/// </summary>
+		/// <returns>The render.</returns>
+		/// <param name="selected">Describes whether the item being rendered is currently selected by the user.</param>
+		/// <param name="item">The index of the item to render, zero for the first item and so on.</param>
+		/// <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>
+		/// <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 (bool selected, int item, int col, int line, int width);
+
+		/// <summary>
+		/// Should return whether the specified item is currently marked.
+		/// </summary>
+		/// <returns><c>true</c>, if marked, <c>false</c> otherwise.</returns>
+		/// <param name="item">Item index.</param>
+		bool IsMarked (int item);
+
+		/// <summary>
+		/// Flags the item as marked.
+		/// </summary>
+		/// <param name="item">Item index.</param>
+		/// <param name="value">If set to <c>true</c> value.</param>
+		void SetMark (int item, bool value);
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
 	/// ListView widget renders a list of data.
 	/// ListView widget renders a list of data.
 	/// </summary>
 	/// </summary>
 	/// <remarks>
 	/// <remarks>
+	/// <para>
+	///   The ListView displays lists of data and allows the user to scroll through the data
+	///   and optionally mark elements of the list (controlled by the AllowsMark property).  
+	/// </para>
+	/// <para>
+	///   The ListView can either render an arbitrary IList object (for example, arrays, List<T>
+	///   and other collections) which are drawn by drawing the string/ustring contents or the 
+	///   result of calling ToString().   Alternatively, you can provide you own IListDataSource
+	///   object that gives you full control of what is rendered.
+	/// </para>
+	/// <para>
+	///   The ListView can display any object that implements the System.Collection.IList interface,
+	///   string values are converted into ustring values before rendering, and other values are
+	///   converted into ustrings by calling ToString() and then converting to ustring.
+	/// </para>
+	/// <para>
+	///   If you must change the contents of the ListView, set the Source property (when you are
+	///   providing your own rendering via the IListDataSource implementation) or call SetSource
+	///   when you are providing an IList.
+	/// </para>
 	/// </remarks>
 	/// </remarks>
 	public class ListView : View {
 	public class ListView : View {
 		int top;
 		int top;
 		int selected;
 		int selected;
 
 
+		//
+		// This class is the built-in IListDataSource that renders arbitrary
+		// IList instances
+		//
 		class ListWrapper : IListDataSource {
 		class ListWrapper : IListDataSource {
 			IList src;
 			IList src;
 			public ListView Container;
 			public ListView Container;
 			public ConsoleDriver Driver;
 			public ConsoleDriver Driver;
+			BitArray marks;
+			int count;
 
 
 			public ListWrapper (IList source)
 			public ListWrapper (IList source)
 			{
 			{
+				count = source.Count;
+				marks = new BitArray (count);
 				this.src = source;
 				this.src = source;
 			}
 			}
 
 
@@ -54,7 +128,7 @@ namespace Terminal.Gui {
 				}
 				}
 			}
 			}
 
 
-			public void Render (int item, int col, int line, int width)
+			public void Render (bool marked, int item, int col, int line, int width)
 			{
 			{
 				Container.Move (col, line);
 				Container.Move (col, line);
 				var t = src [item];
 				var t = src [item];
@@ -65,11 +139,24 @@ namespace Terminal.Gui {
 				} else
 				} else
 					RenderUstr (((string)t).ToString (), col, line, width);
 					RenderUstr (((string)t).ToString (), col, line, width);
 			}
 			}
+
+			public bool IsMarked (int item)
+			{
+				if (item >= 0 && item < count)
+					return marks [item];
+				return false;
+			}
+
+			public void SetMark (int item, bool value)
+			{
+				if (item >= 0 && item < count)
+					marks [item] = value;
+			}
 		}
 		}
 
 
 		IListDataSource source;
 		IListDataSource source;
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the IListDataSource backing this view.
+		/// Gets or sets the IListDataSource backing this view, use SetSource() if you want to set a new IList source.
 		/// </summary>
 		/// </summary>
 		/// <value>The source.</value>
 		/// <value>The source.</value>
 		public IListDataSource Source {
 		public IListDataSource Source {
@@ -84,6 +171,17 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		/// <summary>
+		/// Sets the source to an IList value, if you want to set a full IListDataSource, use the Source property.
+		/// </summary>
+		/// <value>An item implementing the IList interface.</value>
+		public void SetSource (IList source)
+		{
+			if (source == null)
+				throw new ArgumentNullException (nameof (source));
+			Source = MakeWrapper (source);
+		}
+
 		bool allowsMarking;
 		bool allowsMarking;
 		/// <summary>
 		/// <summary>
 		/// Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.ListView"/> allows items to be marked.
 		/// Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.ListView"/> allows items to be marked.
@@ -134,18 +232,119 @@ namespace Terminal.Gui {
 			return new ListWrapper (source);
 			return new ListWrapper (source);
 		}
 		}
 
 
-		public ListView (Rect rect, IList source, (ustring title, int width) [] headers = null) : this (rect, MakeWrapper (source), headers)
+		/// <summary>
+		/// Initializes a new ListView that will display the contents of the object implementing the IList interface.
+		/// </summary>
+		/// <param name="rect">Frame for the listview.</param>
+		/// <param name="source">An IList data source, if the elements of the IList are strings or ustrings, the string is rendered, otherwise the ToString() method is invoked on the result.</param>
+		public ListView (Rect rect, IList source) : this (rect, MakeWrapper (source))
 		{
 		{
 			((ListWrapper)(Source)).Container = this;
 			((ListWrapper)(Source)).Container = this;
 			((ListWrapper)(Source)).Driver = Driver;
 			((ListWrapper)(Source)).Driver = Driver;
 		}
 		}
 
 
-		public ListView (Rect rect, IListDataSource source, (ustring title, int width) [] headers = null) : base (rect)
+		/// <summary>
+		/// Initializes a new ListView that will display the provided data source.
+		/// </summary>
+		/// <param name="rect">Frame for the listview.</param>
+		/// <param name="source">IListDataSource object that provides a mechanism to render the data. The number of elements on the collection should not change, if you must change, set the "Source" property to reset the internal settings of the ListView.</param>
+		public ListView (Rect rect, IListDataSource source) : base (rect)
 		{
 		{
 			if (source == null)
 			if (source == null)
 				throw new ArgumentNullException (nameof (source));
 				throw new ArgumentNullException (nameof (source));
 			Source = source;
 			Source = source;
+			CanFocus = true;
 		}
 		}
 
 
+		public override void Redraw(Rect region)
+		{
+			var current = ColorScheme.Focus;
+			Driver.SetAttribute (current);
+			Move (0, 0);
+			var f = Frame;
+			var item = top;
+			bool focused = HasFocus;
+
+			for (int row = 0; row < f.Height; row++, item++) {
+				bool isSelected = item == selected;
+
+				var newcolor = focused ? (isSelected ? ColorScheme.Focus : ColorScheme.Normal) : ColorScheme.Normal;
+				if (newcolor != current) {
+					Driver.SetAttribute (newcolor);
+					current = newcolor;
+				}
+				if (item >= source.Count)
+					for (int c = 0; c < f.Width; c++)
+						Driver.AddRune (' ');
+				else
+					Source.Render (isSelected, item, 0, row, f.Width);
+
+			}
+		}
+
+		/// <summary>
+		/// This event is raised when the cursor selection has changed.
+		/// </summary>
+		public event Action SelectedChanged;
+
+		public override bool ProcessKey (KeyEvent kb)
+		{
+			switch (kb.Key) {
+			case Key.CursorUp:
+			case Key.ControlP:
+				if (selected > 0) {
+					selected--;
+					if (selected < top)
+						top = selected;
+					if (SelectedChanged != null)
+						SelectedChanged ();
+					SetNeedsDisplay ();
+				}
+				return true;
+
+			case Key.CursorDown:
+			case Key.ControlN:
+				if (selected + 1 < source.Count) {
+					selected++;
+					if (selected >= top + Frame.Height)
+						top++;
+					if (SelectedChanged != null)
+						SelectedChanged ();
+					SetNeedsDisplay ();
+				}
+				return true;
+
+			case Key.ControlV:
+			case Key.PageDown:
+				var n = (selected + Frame.Height);
+				if (n > source.Count)
+					n = source.Count - 1;
+				if (n != selected) {
+					selected = n;
+					if (source.Count >= Frame.Height)
+						top = selected;
+					else
+						top = 0;
+					if (SelectedChanged != null)
+						SelectedChanged ();
+					SetNeedsDisplay ();
+				}
+				return true;
+
+			case Key.PageUp:
+				n = (selected - Frame.Height);
+				if (n < 0)
+					n = 0;
+				if (n != selected) {
+					selected = n;
+					top = selected;
+					if (SelectedChanged != null)
+						SelectedChanged ();
+					SetNeedsDisplay ();
+				}
+				return true;
+			}
+			return base.ProcessKey (kb);
+		}
 	}
 	}
 }
 }

+ 1 - 1
Terminal.csproj

@@ -32,7 +32,7 @@
   <ItemGroup>
   <ItemGroup>
     <Reference Include="System" />
     <Reference Include="System" />
     <Reference Include="NStack">
     <Reference Include="NStack">
-      <HintPath>packages\NStack.Core.0.7.0\lib\netstandard1.5\NStack.dll</HintPath>
+      <HintPath>packages\NStack.Core.0.8.0\lib\netstandard1.5\NStack.dll</HintPath>
       <Private>False</Private>
       <Private>False</Private>
     </Reference>
     </Reference>
   </ItemGroup>
   </ItemGroup>

+ 9 - 0
demo.cs

@@ -93,6 +93,15 @@ class Demo {
 				new CheckBox (1, 0, "Remember me"),
 				new CheckBox (1, 0, "Remember me"),
 				new RadioGroup (1, 2, new [] { "_Personal", "_Company" }),
 				new RadioGroup (1, 2, new [] { "_Personal", "_Company" }),
 			},
 			},
+			new ListView (new Rect (60, 6, 16, 4), new string [] {
+				"First row",
+				"<>",
+				"This is a very long row that should overflow what is shown",
+				"4th",
+				"There is an empty slot on the second row",
+				"Whoa",
+				"This is so cool"
+			}),
 			scrollView,
 			scrollView,
 			//scrollView2,
 			//scrollView2,
 			new Button (3, 19, "Ok"),
 			new Button (3, 19, "Ok"),

+ 1 - 1
packages.config

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
-  <package id="NStack.Core" version="0.7.0" targetFramework="net461" />
+  <package id="NStack.Core" version="0.8.0" targetFramework="net461" />
 </packages>
 </packages>