Răsfoiți Sursa

Merge pull request #1 from migueldeicaza/master

Update
Jamie D 5 ani în urmă
părinte
comite
e041bc087f

+ 29 - 20
Example/demo.cs

@@ -237,12 +237,13 @@ static class Demo {
 	//
 	static void Editor ()
 	{
-		var tframe = Application.Top.Frame;
-		Application.Top.RemoveAll ();
+		Application.Init ();
+
 		var ntop = Application.Top;
+
 		var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_Close", "", () => { if (Quit ()) {Application.RequestStop (); } }),
+				new MenuItem ("_Close", "", () => { if (Quit ()) { running = MainApp; Application.RequestStop (); } }),
 			}),
 			new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuItem ("_Copy", "", null),
@@ -267,15 +268,13 @@ static class Demo {
 		};
 		ntop.Add (win);
 
-		var text = new TextView (new Rect (0, 0, tframe.Width - 2, tframe.Height - 3));
+		var text = new TextView () { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
 
 		if (fname != null)
 			text.Text = System.IO.File.ReadAllText (fname);
 		win.Add (text);
 
 		Application.Run (ntop, false);
-		Application.Top.RemoveAll ();
-		Main ();
 	}
 
 	static bool Quit ()
@@ -450,20 +449,22 @@ static class Demo {
 
 	static void ComboBoxDemo ()
 	{
-		IList<string> items = new List<string> ();
-		foreach (var dir in new [] { "/etc", @"\windows\System32" }) {
+		//TODO: Duplicated code in ListsAndCombos.cs Consider moving to shared assembly
+		var items = new List<ustring> ();
+		foreach (var dir in new [] { "/etc", @$"{Environment.GetEnvironmentVariable ("SystemRoot")}\System32" }) {
 			if (Directory.Exists (dir)) {
 				items = Directory.GetFiles (dir).Union (Directory.GetDirectories (dir))
 					.Select (Path.GetFileName)
 					.Where (x => char.IsLetterOrDigit (x [0]))
-					.OrderBy (x => x).ToList ();
+					.OrderBy (x => x).Select (x => ustring.Make (x)).ToList ();
 			}
 		}
-		var list = new ComboBox () { X = 0, Y = 0, Width = Dim.Fill(), Height = Dim.Fill() };
-		list.SetSource(items.ToList());
-		list.SelectedItemChanged += (object sender, ustring text) => { Application.RequestStop (); };
+		var list = new ComboBox () { Width = Dim.Fill(), Height = Dim.Fill() };
+		list.SetSource(items);
+		list.OpenSelectedItem += (ListViewItemEventArgs text) => { Application.RequestStop (); };
 
-		var d = new Dialog ("Select source file", 40, 12) { list };
+		var d = new Dialog () { Title = "Select source file", Width = Dim.Percent (50), Height = Dim.Percent (50) };
+		d.Add (list);
 		Application.Run (d);
 
 		MessageBox.Query (60, 10, "Selected file", list.Text.ToString() == "" ? "Nothing selected" : list.Text.ToString(), "Ok");
@@ -534,11 +535,20 @@ static class Demo {
 	}
 	#endregion
 
+	public static Action running = MainApp;
+	static void Main ()
+	{
+		while (running != null) {
+			running.Invoke ();
+		}
+		Application.Shutdown ();
+	}
+
 	public static Label ml;
 	public static MenuBar menu;
 	public static CheckBox menuKeysStyle;
 	public static CheckBox menuAutoMouseNav;
-	static void Main ()
+	static void MainApp ()
 	{
 		if (Debugger.IsAttached)
 			CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
@@ -578,14 +588,14 @@ static class Demo {
 
 		menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("Text _Editor Demo", "", () => { Editor (); }),
+				new MenuItem ("Text _Editor Demo", "", () => { running = Editor; Application.RequestStop (); }),
 				new MenuItem ("_New", "Creates new file", NewFile),
 				new MenuItem ("_Open", "", Open),
 				new MenuItem ("_Hex", "", () => ShowHex (top)),
 				new MenuItem ("_Close", "", () => Close ()),
 				new MenuItem ("_Disabled", "", () => { }, () => false),
 				null,
-				new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
+				new MenuItem ("_Quit", "", () => { if (Quit ()) { running = null; top.Running = false; } })
 			}),
 			new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuItem ("_Copy", "", Copy),
@@ -648,9 +658,8 @@ static class Demo {
 			new StatusItem(Key.F1, "~F1~ Help", () => Help()),
 			new StatusItem(Key.F2, "~F2~ Load", Load),
 			new StatusItem(Key.F3, "~F3~ Save", Save),
-			new StatusItem(Key.ControlQ, "~^Q~ Quit", () => { if (Quit ()) top.Running = false; }),
-		}) {
-		};
+			new StatusItem(Key.ControlQ, "~^Q~ Quit", () => { if (Quit ()) { running = null; top.Running = false; } })
+		});
 
 		win.Add (drag, dragText);
 
@@ -671,7 +680,7 @@ static class Demo {
 		top.Add (win);
 		//top.Add (menu);
 		top.Add (menu, statusBar);
-		Application.Run ();
+		Application.Run (top, false);
 	}
 
 	private static void Win_KeyPress (View.KeyEventEventArgs e)

+ 13 - 4
Terminal.Gui/Core/Application.cs

@@ -241,6 +241,7 @@ namespace Terminal.Gui {
 			{
 				if (Toplevel != null) {
 					End (Toplevel, disposing);
+					Toplevel.Dispose ();
 					Toplevel = null;
 				}
 			}
@@ -500,6 +501,7 @@ namespace Terminal.Gui {
 			// TODO: Some of this state is actually related to Begin/End (not Init/Shutdown) and should be moved to `RunState` (#520)
 			foreach (var t in toplevels) {
 				t.Running = false;
+				t.Dispose ();
 			}
 			toplevels.Clear ();
 			Current = null;
@@ -586,16 +588,23 @@ namespace Terminal.Gui {
 
 					MainLoop.MainIteration ();
 					Iteration?.Invoke ();
-				} else if (wait == false)
+				} else if (wait == false) {
 					return;
-				if (state.Toplevel.NeedDisplay != null && (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay)) {
+				}
+				if (state.Toplevel != Top && (!Top.NeedDisplay.IsEmpty || Top.childNeedsDisplay)) {
+					Top.Redraw (Top.Bounds);
+					state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds);
+				}
+				if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
 					state.Toplevel.Redraw (state.Toplevel.Bounds);
-					if (DebugDrawBounds)
+					if (DebugDrawBounds) {
 						DrawBounds (state.Toplevel);
+					}
 					state.Toplevel.PositionCursor ();
 					Driver.Refresh ();
-				} else
+				} else {
 					Driver.UpdateCursor ();
+				}
 			}
 		}
 

+ 72 - 5
Terminal.Gui/Core/Responder.cs

@@ -13,11 +13,39 @@
 // Optimziations
 //   - Add rendering limitation to the exposed area
 
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
 namespace Terminal.Gui {
 	/// <summary>
 	/// Responder base class implemented by objects that want to participate on keyboard and mouse input.
 	/// </summary>
-	public class Responder {
+	public class Responder : IDisposable {
+		bool disposedValue;
+
+#if DEBUG_IDISPOSABLE
+		/// <summary>
+		/// For debug purposes to verify objects are being disposed properly
+		/// </summary>
+		public bool WasDisposed = false;
+		/// <summary>
+		/// For debug purposes to verify objects are being disposed properly
+		/// </summary>
+		public int DisposedCount = 0;
+		/// <summary>
+		/// For debug purposes
+		/// </summary>
+		public static List<Responder> Instances = new List<Responder> ();
+		/// <summary>
+		/// For debug purposes
+		/// </summary>
+		public Responder ()
+		{
+			Instances.Add (this);
+		}
+#endif
+
 		/// <summary>
 		/// Gets or sets a value indicating whether this <see cref="Responder"/> can focus.
 		/// </summary>
@@ -28,7 +56,7 @@ namespace Terminal.Gui {
 		/// Gets or sets a value indicating whether this <see cref="Responder"/> has focus.
 		/// </summary>
 		/// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
-		public virtual bool HasFocus { get; internal set; }
+		public virtual bool HasFocus { get; }
 
 		// Key handling
 		/// <summary>
@@ -133,7 +161,6 @@ namespace Terminal.Gui {
 			return false;
 		}
 
-
 		/// <summary>
 		/// Method invoked when a mouse event is generated
 		/// </summary>
@@ -167,8 +194,9 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Method invoked when a view gets focus.
 		/// </summary>
+		/// <param name="view">The view that is losing focus.</param>
 		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public virtual bool OnEnter ()
+		public virtual bool OnEnter (View view)
 		{
 			return false;
 		}
@@ -176,10 +204,49 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Method invoked when a view loses focus.
 		/// </summary>
+		/// <param name="view">The view that is getting focus.</param>
 		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public virtual bool OnLeave ()
+		public virtual bool OnLeave (View view)
 		{
 			return false;
 		}
+
+		/// <summary>
+		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+		/// </summary>
+		/// <remarks>
+		/// If disposing equals true, the method has been called directly
+		/// or indirectly by a user's code. Managed and unmanaged resources
+		/// can be disposed.
+		/// If disposing equals false, the method has been called by the
+		/// runtime from inside the finalizer and you should not reference
+		/// other objects. Only unmanaged resources can be disposed.		
+		/// </remarks>
+		/// <param name="disposing"></param>
+		protected virtual void Dispose (bool disposing)
+		{
+			if (!disposedValue) {
+				if (disposing) {
+					// TODO: dispose managed state (managed objects)
+				}
+
+				// TODO: free unmanaged resources (unmanaged objects) and override finalizer
+				// TODO: set large fields to null
+				disposedValue = true;
+			}
+		}
+
+		/// <summary>
+		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.
+		/// </summary>
+		public void Dispose ()
+		{
+			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+			Dispose (disposing: true);
+			GC.SuppressFinalize (this);
+#if DEBUG_IDISPOSABLE
+			WasDisposed = true;
+#endif
+		}
 	}
 }

+ 50 - 30
Terminal.Gui/Core/TextFormatter.cs

@@ -35,7 +35,7 @@ namespace Terminal.Gui {
 		ustring text;
 		TextAlignment textAlignment;
 		Attribute textColor = -1;
-		bool needsFormat = true;
+		bool needsFormat;
 		Key hotKey;
 		Size size;
 
@@ -46,7 +46,14 @@ namespace Terminal.Gui {
 			get => text;
 			set {
 				text = value;
-				needsFormat = true;
+
+				if (Size.IsEmpty) {
+					// Proivde a default size (width = length of longest line, height = 1)
+					// TODO: It might makem more sense for the default to be width = length of first line?
+					Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
+				}
+
+				NeedsFormat = true;
 			}
 		}
 
@@ -59,18 +66,18 @@ namespace Terminal.Gui {
 			get => textAlignment;
 			set {
 				textAlignment = value;
-				needsFormat = true;
+				NeedsFormat = true;
 			}
 		}
 
 		/// <summary>
-		///  Gets the size of the area the text will be drawn in. 
+		///  Gets or sets the size of the area the text will be constrainted to when formatted. 
 		/// </summary>
 		public Size Size {
 			get => size;
-			internal set {
+			set {
 				size = value;
-				needsFormat = true;
+				NeedsFormat = true;
 			}
 		}
 
@@ -96,40 +103,50 @@ namespace Terminal.Gui {
 		public uint HotKeyTagMask { get; set; } = 0x100000;
 
 		/// <summary>
-		/// Gets the formatted lines.
+		/// Gets the formatted lines. 
 		/// </summary>
+		/// <remarks>
+		/// <para>
+		/// Upon a 'get' of this property, if the text needs to be formatted (if <see cref="NeedsFormat"/> is <c>true</c>)
+		/// <see cref="Format(ustring, int, TextAlignment, bool)"/> will be called internally. 
+		/// </para>
+		/// </remarks>
 		public List<ustring> Lines {
 			get {
 				// With this check, we protect against subclasses with overrides of Text
 				if (ustring.IsNullOrEmpty (Text)) {
 					lines = new List<ustring> ();
 					lines.Add (ustring.Empty);
-					needsFormat = false;
+					NeedsFormat = false;
 					return lines;
 				}
 
-				if (needsFormat) {
+				if (NeedsFormat) {
 					var shown_text = text;
 					if (FindHotKey (text, HotKeySpecifier, true, out hotKeyPos, out hotKey)) {
 						shown_text = RemoveHotKeySpecifier (Text, hotKeyPos, HotKeySpecifier);
 						shown_text = ReplaceHotKeyWithTag (shown_text, hotKeyPos);
 					}
+					if (Size.IsEmpty) {
+						throw new InvalidOperationException ("Size must be set before accessing Lines");
+					}
 					lines = Format (shown_text, Size.Width, textAlignment, Size.Height > 1);
+					NeedsFormat = false;
 				}
-				needsFormat = false;
 				return lines;
 			}
 		}
 
 		/// <summary>
-		/// Sets a flag indicating the text needs to be formatted. 
-		/// Subsequent calls to <see cref="Draw"/>, <see cref="Lines"/>, etc... will cause the formatting to happen.>
+		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute)"/> is called. 
+		/// If it is <c>false</c> when Draw is called, the Draw call will be faster.
 		/// </summary>
-		public void SetNeedsFormat ()
-		{
-			needsFormat = true;
-		}
-
+		/// <remarks>
+		/// <para>
+		/// This is set to true when the properties of <see cref="TextFormatter"/> are set. 
+		/// </para>
+		/// </remarks>
+		public bool NeedsFormat { get => needsFormat; set => needsFormat = value; }
 
 		static ustring StripCRLF (ustring str)
 		{
@@ -199,14 +216,14 @@ namespace Terminal.Gui {
 				return lines;
 			}
 
-			var runes = StripCRLF (text).ToRuneList();
+			var runes = StripCRLF (text).ToRuneList ();
 
 			while ((end = start + width) < runes.Count) {
 				while (runes [end] != ' ' && end > start)
 					end -= 1;
 				if (end == start)
 					end = start + width;
-				lines.Add (ustring.Make (runes.GetRange (start, end - start)).TrimSpace());
+				lines.Add (ustring.Make (runes.GetRange (start, end - start)).TrimSpace ());
 				start = end;
 			}
 
@@ -236,7 +253,7 @@ namespace Terminal.Gui {
 			var runes = text.ToRuneList ();
 			int slen = runes.Count;
 			if (slen > width) {
-				return ustring.Make (runes.GetRange(0, width));
+				return ustring.Make (runes.GetRange (0, width));
 			} else {
 				if (talign == TextAlignment.Justified) {
 					return Justify (text, width);
@@ -303,6 +320,9 @@ namespace Terminal.Gui {
 		/// <para>
 		/// If <c>width</c> is 0, a single, empty line will be returned.
 		/// </para>
+		/// <para>
+		/// If <c>width</c> is int.MaxValue, the text will be formatted to the maximum width possible. 
+		/// </para>
 		/// </remarks>
 		public static List<ustring> Format (ustring text, int width, TextAlignment talign, bool wordWrap)
 		{
@@ -329,7 +349,7 @@ namespace Terminal.Gui {
 			for (int i = 0; i < runeCount; i++) {
 				Rune c = text [i];
 				if (c == '\n') {
-					var wrappedLines = WordWrap (ustring.Make (runes.GetRange(lp, i - lp)), width);
+					var wrappedLines = WordWrap (ustring.Make (runes.GetRange (lp, i - lp)), width);
 					foreach (var line in wrappedLines) {
 						lineResult.Add (ClipAndJustify (line, width, talign));
 					}
@@ -339,7 +359,7 @@ namespace Terminal.Gui {
 					lp = i + 1;
 				}
 			}
-			foreach (var line in WordWrap (ustring.Make (runes.GetRange(lp, runeCount - lp)), width)) {
+			foreach (var line in WordWrap (ustring.Make (runes.GetRange (lp, runeCount - lp)), width)) {
 				lineResult.Add (ClipAndJustify (line, width, talign));
 			}
 
@@ -359,7 +379,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Computes the maximum width needed to render the text (single line or multple lines).
+		/// Computes the maximum width needed to render the text (single line or multple lines) given a minimium width.
 		/// </summary>
 		/// <returns>Max width of lines.</returns>
 		/// <param name="text">Text, may contain newlines.</param>
@@ -528,12 +548,12 @@ namespace Terminal.Gui {
 		/// <param name="hotColor">The color to use to draw the hotkey</param>
 		public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor)
 		{
-			// With this check, we protect against subclasses with overrides of Text
+			// With this check, we protect against subclasses with overrides of Text (like Button)
 			if (ustring.IsNullOrEmpty (text)) {
 				return;
 			}
 
-			Application.Driver.SetAttribute (normalColor);
+			Application.Driver?.SetAttribute (normalColor);
 
 			// Use "Lines" to ensure a Format (don't use "lines"))
 			for (int line = 0; line < Lines.Count; line++) {
@@ -558,17 +578,17 @@ namespace Terminal.Gui {
 					throw new ArgumentOutOfRangeException ();
 				}
 				for (var col = bounds.Left; col < bounds.Left + bounds.Width; col++) {
-					Application.Driver.Move (col, bounds.Top + line);
+					Application.Driver?.Move (col, bounds.Top + line);
 					var rune = (Rune)' ';
 					if (col >= x && col < (x + runes.Length)) {
 						rune = runes [col - x];
 					}
 					if ((rune & HotKeyTagMask) == HotKeyTagMask) {
-						Application.Driver.SetAttribute (hotColor);
-						Application.Driver.AddRune ((Rune)((uint)rune & ~HotKeyTagMask));
-						Application.Driver.SetAttribute (normalColor);
+						Application.Driver?.SetAttribute (hotColor);
+						Application.Driver?.AddRune ((Rune)((uint)rune & ~HotKeyTagMask));
+						Application.Driver?.SetAttribute (normalColor);
 					} else {
-						Application.Driver.AddRune (rune);
+						Application.Driver?.AddRune (rune);
 					}
 				}
 			}

+ 25 - 8
Terminal.Gui/Core/Toplevel.cs

@@ -229,10 +229,14 @@ namespace Terminal.Gui {
 		public override void Remove (View view)
 		{
 			if (this == Application.Top) {
-				if (view is MenuBar)
+				if (view is MenuBar) {
+					MenuBar?.Dispose ();
 					MenuBar = null;
-				if (view is StatusBar)
+				}
+				if (view is StatusBar) {
 					StatusBar = null;
+					StatusBar = null;
+				}
 			}
 			base.Remove (view);
 		}
@@ -241,7 +245,9 @@ namespace Terminal.Gui {
 		public override void RemoveAll ()
 		{
 			if (this == Application.Top) {
+				MenuBar?.Dispose ();
 				MenuBar = null;
+				StatusBar?.Dispose ();
 				StatusBar = null;
 			}
 			base.RemoveAll ();
@@ -252,17 +258,28 @@ namespace Terminal.Gui {
 			nx = Math.Max (x, 0);
 			nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx;
 			bool m, s;
-			if (SuperView == null || SuperView.GetType () != typeof (Toplevel))
+			if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
 				m = Application.Top.MenuBar != null;
-			else
+			} else {
 				m = ((Toplevel)SuperView).MenuBar != null;
-			int l = m ? 1 : 0;
+			}
+			int l;
+			if (SuperView == null || SuperView is Toplevel) {
+				l = m ? 1 : 0;
+			} else {
+				l = 0;
+			}
 			ny = Math.Max (y, l);
-			if (SuperView == null || SuperView.GetType () != typeof (Toplevel))
+			if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
 				s = Application.Top.StatusBar != null;
-			else
+			} else {
 				s = ((Toplevel)SuperView).StatusBar != null;
-			l = s ? Driver.Rows - 1 : Driver.Rows;
+			}
+			if (SuperView == null || SuperView is Toplevel) {
+				l = s ? Driver.Rows - 1 : Driver.Rows;
+			} else {
+				l = s ? SuperView.Frame.Height - 1 : SuperView.Frame.Height;
+			}
 			ny = Math.Min (ny, l);
 			ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
 		}

+ 220 - 63
Terminal.Gui/Core/View.cs

@@ -122,7 +122,17 @@ namespace Terminal.Gui {
 		View focused = null;
 		Direction focusDirection;
 
-		TextFormatter viewText;
+		TextFormatter textFormatter;
+
+		/// <summary>
+		/// Event fired when a subview is being added to this view.
+		/// </summary>
+		public Action<View> Added;
+
+		/// <summary>
+		/// Event fired when a subview is being removed from this view.
+		/// </summary>
+		public Action<View> Removed;
 
 		/// <summary>
 		/// Event fired when the view gets focus.
@@ -152,12 +162,12 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire.
 		/// </summary>
-		public Key HotKey { get => viewText.HotKey; set => viewText.HotKey = value; }
+		public Key HotKey { get => textFormatter.HotKey; set => textFormatter.HotKey = value; }
 
 		/// <summary>
 		/// Gets or sets the specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'. 
 		/// </summary>
-		public Rune HotKeySpecifier { get => viewText.HotKeySpecifier; set => viewText.HotKeySpecifier = value; }
+		public Rune HotKeySpecifier { get => textFormatter.HotKeySpecifier; set => textFormatter.HotKeySpecifier = value; }
 
 		internal Direction FocusDirection {
 			get => SuperView?.FocusDirection ?? focusDirection;
@@ -190,6 +200,97 @@ namespace Terminal.Gui {
 		// to make the same mistakes our users make when they poke at the Subviews.
 		internal IList<View> InternalSubviews => subviews ?? empty;
 
+		// This is null, and allocated on demand.
+		List<View> tabIndexes;
+
+		/// <summary>
+		/// This returns a tab index list of the subviews contained by this view.
+		/// </summary>
+		/// <value>The tabIndexes.</value>
+		public IList<View> TabIndexes => tabIndexes == null ? empty : tabIndexes.AsReadOnly ();
+
+		int tabIndex = -1;
+
+		/// <summary>
+		/// Indicates the index of the current <see cref="View"/> from the <see cref="TabIndexes"/> list.
+		/// </summary>
+		public int TabIndex {
+			get { return tabIndex; }
+			set {
+				if (!CanFocus) {
+					tabIndex = -1;
+					return;
+				} else if (SuperView?.tabIndexes == null || SuperView?.tabIndexes.Count == 1) {
+					tabIndex = 0;
+					return;
+				} else if (tabIndex == value) {
+					return;
+				}
+				tabIndex = value > SuperView.tabIndexes.Count - 1 ? SuperView.tabIndexes.Count - 1 : value < 0 ? 0 : value;
+				tabIndex = GetTabIndex (tabIndex);
+				if (SuperView.tabIndexes.IndexOf (this) != tabIndex) {
+					SuperView.tabIndexes.Remove (this);
+					SuperView.tabIndexes.Insert (tabIndex, this);
+					SetTabIndex ();
+				}
+			}
+		}
+
+		private int GetTabIndex (int idx)
+		{
+			int i = 0;
+			foreach (var v in SuperView.tabIndexes) {
+				if (v.tabIndex == -1 || v == this) {
+					continue;
+				}
+				i++;
+			}
+			return Math.Min (i, idx);
+		}
+
+		private void SetTabIndex ()
+		{
+			int i = 0;
+			foreach (var v in SuperView.tabIndexes) {
+				if (v.tabIndex == -1) {
+					continue;
+				}
+				v.tabIndex = i;
+				i++;
+			}
+		}
+
+		bool tabStop = true;
+
+		/// <summary>
+		/// This only be <c>true</c> if the <see cref="CanFocus"/> is also <c>true</c> and the focus can be avoided by setting this to <c>false</c>
+		/// </summary>
+		public bool TabStop {
+			get { return tabStop; }
+			set {
+				if (tabStop == value) {
+					return;
+				}
+				tabStop = CanFocus && value;
+			}
+		}
+
+		/// <inheritdoc/>
+		public override bool CanFocus {
+			get => base.CanFocus;
+			set {
+				if (base.CanFocus != value) {
+					base.CanFocus = value;
+					if (!value && tabIndex > -1) {
+						TabIndex = -1;
+					} else if (value && tabIndex == -1) {
+						TabIndex = SuperView != null ? SuperView.tabIndexes.IndexOf (this) : -1;
+					}
+					TabStop = value;
+				}
+			}
+		}
+
 		internal Rect NeedDisplay { get; private set; } = Rect.Empty;
 
 		// The frame for the object. Superview relative.
@@ -381,7 +482,7 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public View (Rect frame)
 		{
-			viewText = new TextFormatter ();
+			textFormatter = new TextFormatter ();
 			this.Text = ustring.Empty;
 
 			this.Frame = frame;
@@ -444,7 +545,7 @@ namespace Terminal.Gui {
 		/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
 		public View (Rect rect, ustring text) : this (rect)
 		{
-			viewText = new TextFormatter ();
+			textFormatter = new TextFormatter ();
 			this.Text = text;
 		}
 
@@ -464,10 +565,12 @@ namespace Terminal.Gui {
 		/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
 		public View (ustring text) : base ()
 		{
-			viewText = new TextFormatter ();
+			textFormatter = new TextFormatter ();
 			this.Text = text;
 
 			CanFocus = false;
+			TabIndex = -1;
+			TabStop = false;
 			LayoutStyle = LayoutStyle.Computed;
 			// BUGBUG: CalcRect doesn't account for line wrapping
 			var r = TextFormatter.CalcRect (0, 0, text);
@@ -495,7 +598,7 @@ namespace Terminal.Gui {
 			if (SuperView == null)
 				return;
 			SuperView.SetNeedsLayout ();
-			viewText.SetNeedsFormat ();
+			textFormatter.NeedsFormat = true;
 		}
 
 		/// <summary>
@@ -548,12 +651,21 @@ namespace Terminal.Gui {
 		{
 			if (view == null)
 				return;
-			if (subviews == null)
+			if (subviews == null) {
 				subviews = new List<View> ();
+			}
+			if (tabIndexes == null) {
+				tabIndexes = new List<View> ();
+			}
 			subviews.Add (view);
+			tabIndexes.Add (view);
 			view.container = this;
-			if (view.CanFocus)
+			OnAdded (view);
+			if (view.CanFocus) {
 				CanFocus = true;
+				view.tabIndex = tabIndexes.IndexOf (view);
+			}
+
 			SetNeedsLayout ();
 			SetNeedsDisplay ();
 		}
@@ -583,6 +695,7 @@ namespace Terminal.Gui {
 
 			while (subviews.Count > 0) {
 				Remove (subviews [0]);
+				Remove (tabIndexes [0]);
 			}
 		}
 
@@ -600,8 +713,10 @@ namespace Terminal.Gui {
 			SetNeedsDisplay ();
 			var touched = view.Frame;
 			subviews.Remove (view);
+			tabIndexes.Remove (view);
 			view.container = null;
-
+			OnRemoved (view);
+			view.tabIndex = -1;
 			if (subviews.Count < 1)
 				this.CanFocus = false;
 
@@ -888,33 +1003,38 @@ namespace Terminal.Gui {
 				focused.PositionCursor ();
 			else {
 				if (CanFocus && HasFocus) {
-					Move (viewText.HotKeyPos == -1 ? 1 : viewText.HotKeyPos, 0);
+					Move (textFormatter.HotKeyPos == -1 ? 1 : textFormatter.HotKeyPos, 0);
 				} else {
 					Move (frame.X, frame.Y);
 				}
 			}
 		}
 
+		bool hasFocus;
 		/// <inheritdoc/>
 		public override bool HasFocus {
 			get {
-				return base.HasFocus;
+				return hasFocus;
 			}
-			internal set {
-				if (base.HasFocus != value)
-					if (value)
-						OnEnter ();
-					else
-						OnLeave ();
-				SetNeedsDisplay ();
-				base.HasFocus = value;
+		}
 
-				// Remove focus down the chain of subviews if focus is removed
-				if (!value && focused != null) {
-					focused.OnLeave ();
-					focused.HasFocus = false;
-					focused = null;
-				}
+		void SetHasFocus (bool value, View view)
+		{
+			if (hasFocus != value) {
+				hasFocus = value;
+			}
+			if (value) {
+				OnEnter (view);
+			} else {
+				OnLeave (view);
+			}
+			SetNeedsDisplay ();
+
+			// Remove focus down the chain of subviews if focus is removed
+			if (!value && focused != null) {
+				focused.OnLeave (view);
+				focused.SetHasFocus (false, view);
+				focused = null;
 			}
 		}
 
@@ -925,35 +1045,58 @@ namespace Terminal.Gui {
 			/// <summary>
 			/// Constructs.
 			/// </summary>
-			public FocusEventArgs () { }
+			/// <param name="view">The view that gets or loses focus.</param>
+			public FocusEventArgs (View view) { View = view; }
 			/// <summary>
 			/// Indicates if the current focus event has already been processed and the driver should stop notifying any other event subscriber.
 			/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
 			/// </summary>
 			public bool Handled { get; set; }
+			/// <summary>
+			/// Indicates the current view that gets or loses focus.
+			/// </summary>
+			public View View { get; set; }
+		}
+
+		/// <summary>
+		/// Method invoked  when a subview is being added to this view.
+		/// </summary>
+		/// <param name="view">The subview being added.</param>
+		public virtual void OnAdded (View view)
+		{
+			view.Added?.Invoke (this);
+		}
+
+		/// <summary>
+		/// Method invoked when a subview is being removed from this view.
+		/// </summary>
+		/// <param name="view">The subview being removed.</param>
+		public virtual void OnRemoved (View view)
+		{
+			view.Removed?.Invoke (this);
 		}
 
 		/// <inheritdoc/>
-		public override bool OnEnter ()
+		public override bool OnEnter (View view)
 		{
-			FocusEventArgs args = new FocusEventArgs ();
+			FocusEventArgs args = new FocusEventArgs (view);
 			Enter?.Invoke (args);
 			if (args.Handled)
 				return true;
-			if (base.OnEnter ())
+			if (base.OnEnter (view))
 				return true;
 
 			return false;
 		}
 
 		/// <inheritdoc/>
-		public override bool OnLeave ()
+		public override bool OnLeave (View view)
 		{
-			FocusEventArgs args = new FocusEventArgs ();
+			FocusEventArgs args = new FocusEventArgs (view);
 			Leave?.Invoke (args);
 			if (args.Handled)
 				return true;
-			if (base.OnLeave ())
+			if (base.OnLeave (view))
 				return true;
 
 			return false;
@@ -1048,8 +1191,10 @@ namespace Terminal.Gui {
 			if (!ustring.IsNullOrEmpty (Text)) {
 				Clear ();
 				// Draw any Text
-				viewText?.SetNeedsFormat ();
-				viewText?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : ColorScheme.Normal, HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
+				if (textFormatter != null) {
+					textFormatter.NeedsFormat = true;
+				}
+				textFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : ColorScheme.Normal, HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
 			}
 
 			// Invoke DrawContentEvent
@@ -1123,10 +1268,11 @@ namespace Terminal.Gui {
 				throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
 
 			if (focused != null)
-				focused.HasFocus = false;
+				focused.SetHasFocus (false, view);
 
+			var f = focused;
 			focused = view;
-			focused.HasFocus = true;
+			focused.SetHasFocus (true, f);
 			focused.EnsureFocus ();
 
 			// Send focus upwards
@@ -1260,13 +1406,13 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void FocusFirst ()
 		{
-			if (subviews == null) {
+			if (tabIndexes == null) {
 				SuperView?.SetFocus (this);
 				return;
 			}
 
-			foreach (var view in subviews) {
-				if (view.CanFocus) {
+			foreach (var view in tabIndexes) {
+				if (view.CanFocus && view.tabStop) {
 					SetFocus (view);
 					return;
 				}
@@ -1278,16 +1424,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void FocusLast ()
 		{
-			if (subviews == null) {
+			if (tabIndexes == null) {
 				SuperView?.SetFocus (this);
 				return;
 			}
 
-			for (int i = subviews.Count; i > 0;) {
+			for (int i = tabIndexes.Count; i > 0;) {
 				i--;
 
-				View v = subviews [i];
-				if (v.CanFocus) {
+				View v = tabIndexes [i];
+				if (v.CanFocus && v.tabStop) {
 					SetFocus (v);
 					return;
 				}
@@ -1301,7 +1447,7 @@ namespace Terminal.Gui {
 		public bool FocusPrev ()
 		{
 			FocusDirection = Direction.Backward;
-			if (subviews == null || subviews.Count == 0)
+			if (tabIndexes == null || tabIndexes.Count == 0)
 				return false;
 
 			if (focused == null) {
@@ -1309,9 +1455,9 @@ namespace Terminal.Gui {
 				return focused != null;
 			}
 			int focused_idx = -1;
-			for (int i = subviews.Count; i > 0;) {
+			for (int i = tabIndexes.Count; i > 0;) {
 				i--;
-				View w = subviews [i];
+				View w = tabIndexes [i];
 
 				if (w.HasFocus) {
 					if (w.FocusPrev ())
@@ -1319,10 +1465,10 @@ namespace Terminal.Gui {
 					focused_idx = i;
 					continue;
 				}
-				if (w.CanFocus && focused_idx != -1) {
-					focused.HasFocus = false;
+				if (w.CanFocus && focused_idx != -1 && w.tabStop) {
+					focused.SetHasFocus (false, w);
 
-					if (w != null && w.CanFocus)
+					if (w != null && w.CanFocus && w.tabStop)
 						w.FocusLast ();
 
 					SetFocus (w);
@@ -1330,7 +1476,7 @@ namespace Terminal.Gui {
 				}
 			}
 			if (focused != null) {
-				focused.HasFocus = false;
+				focused.SetHasFocus (false, this);
 				focused = null;
 			}
 			return false;
@@ -1343,17 +1489,17 @@ namespace Terminal.Gui {
 		public bool FocusNext ()
 		{
 			FocusDirection = Direction.Forward;
-			if (subviews == null || subviews.Count == 0)
+			if (tabIndexes == null || tabIndexes.Count == 0)
 				return false;
 
 			if (focused == null) {
 				FocusFirst ();
 				return focused != null;
 			}
-			int n = subviews.Count;
+			int n = tabIndexes.Count;
 			int focused_idx = -1;
 			for (int i = 0; i < n; i++) {
-				View w = subviews [i];
+				View w = tabIndexes [i];
 
 				if (w.HasFocus) {
 					if (w.FocusNext ())
@@ -1361,10 +1507,10 @@ namespace Terminal.Gui {
 					focused_idx = i;
 					continue;
 				}
-				if (w.CanFocus && focused_idx != -1) {
-					focused.HasFocus = false;
+				if (w.CanFocus && focused_idx != -1 && w.tabStop) {
+					focused.SetHasFocus (false, w);
 
-					if (w != null && w.CanFocus)
+					if (w != null && w.CanFocus && w.tabStop)
 						w.FocusFirst ();
 
 					SetFocus (w);
@@ -1372,7 +1518,7 @@ namespace Terminal.Gui {
 				}
 			}
 			if (focused != null) {
-				focused.HasFocus = false;
+				focused.SetHasFocus (false, this);
 				focused = null;
 			}
 			return false;
@@ -1531,7 +1677,7 @@ namespace Terminal.Gui {
 			Rect oldBounds = Bounds;
 			OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
 
-			viewText.Size = Bounds.Size;
+			textFormatter.Size = Bounds.Size;
 
 
 			// Sort out the dependencies of the X, Y, Width, Height properties
@@ -1592,9 +1738,9 @@ namespace Terminal.Gui {
 		/// </para>
 		/// </remarks>
 		public virtual ustring Text {
-			get => viewText.Text;
+			get => textFormatter.Text;
 			set {
-				viewText.Text = value;
+				textFormatter.Text = value;
 				SetNeedsDisplay ();
 			}
 		}
@@ -1604,9 +1750,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <value>The text alignment.</value>
 		public virtual TextAlignment TextAlignment {
-			get => viewText.Alignment;
+			get => textFormatter.Alignment;
 			set {
-				viewText.Alignment = value;
+				textFormatter.Alignment = value;
 				SetNeedsDisplay ();
 			}
 		}
@@ -1691,5 +1837,16 @@ namespace Terminal.Gui {
 			}
 			return false;
 		}
+
+		/// <inheritdoc/>
+		protected override void Dispose (bool disposing)
+		{
+			for (int i = InternalSubviews.Count - 1; i >= 0; i--) {
+				View subview = InternalSubviews [i];
+				Remove (subview);
+				subview.Dispose ();
+			}
+			base.Dispose (disposing);
+		}
 	}
 }

+ 17 - 17
Terminal.Gui/Core/Window.cs

@@ -205,8 +205,21 @@ namespace Terminal.Gui {
 			// a pending mouse event activated.
 
 			int nx, ny;
-			if ((mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
-				mouseEvent.Flags == MouseFlags.Button3Pressed)) {
+			if (!dragPosition.HasValue && mouseEvent.Flags == (MouseFlags.Button1Pressed)) {
+				// Only start grabbing if the user clicks on the title bar.
+				if (mouseEvent.Y == 0) {
+					start = new Point (mouseEvent.X, mouseEvent.Y);
+					dragPosition = new Point ();
+					nx = mouseEvent.X - mouseEvent.OfX;
+					ny = mouseEvent.Y - mouseEvent.OfY;
+					dragPosition = new Point (nx, ny);
+					Application.GrabMouse (this);
+				}
+
+				//Demo.ml2.Text = $"Starting at {dragPosition}";
+				return true;
+			} else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
+				mouseEvent.Flags == MouseFlags.Button3Pressed) {
 				if (dragPosition.HasValue) {
 					if (SuperView == null) {
 						Application.Top.SetNeedsDisplay (Frame);
@@ -217,8 +230,8 @@ namespace Terminal.Gui {
 					} else {
 						SuperView.SetNeedsDisplay (Frame);
 					}
-					EnsureVisibleBounds (this, mouseEvent.X + mouseEvent.OfX - start.X,
-						mouseEvent.Y + mouseEvent.OfY, out nx, out ny);
+					EnsureVisibleBounds (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - start.X : Frame.X - start.X),
+						mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny);
 
 					dragPosition = new Point (nx, ny);
 					Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
@@ -229,19 +242,6 @@ namespace Terminal.Gui {
 					// FIXED: optimize, only SetNeedsDisplay on the before/after regions.
 					SetNeedsDisplay ();
 					return true;
-				} else {
-					// Only start grabbing if the user clicks on the title bar.
-					if (mouseEvent.Y == 0) {
-						start = new Point (mouseEvent.X, mouseEvent.Y);
-						dragPosition = new Point ();
-						nx = mouseEvent.X - mouseEvent.OfX;
-						ny = mouseEvent.Y - mouseEvent.OfY;
-						dragPosition = new Point (nx, ny);
-						Application.GrabMouse (this);
-					}
-
-					//Demo.ml2.Text = $"Starting at {dragPosition}";
-					return true;
 				}
 			}
 

+ 12 - 0
Terminal.Gui/Terminal.Gui.csproj

@@ -187,6 +187,18 @@
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType></DebugType>
   </PropertyGroup>
+
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
+  </PropertyGroup>
+
+  <!--<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net472|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net472|AnyCPU'">
+    <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
+  </PropertyGroup>-->
   <ItemGroup>
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="true" />
     <PackageReference Include="NStack.Core" Version="0.14.0" />

+ 292 - 154
Terminal.Gui/Views/ComboBox.cs

@@ -4,15 +4,10 @@
 // Authors:
 //   Ross Ferguson ([email protected])
 //
-// TODO:
-//  LayoutComplete() resize Height implement
-//	Cursor rolls of end of list when Height = Dim.Fill() and list fills frame
-//
 
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.Linq;
 using NStack;
 
 namespace Terminal.Gui {
@@ -33,7 +28,12 @@ namespace Terminal.Gui {
 			get => source;
 			set {
 				source = value;
-				SetNeedsDisplay ();
+
+				// Only need to refresh list if its been added to a container view
+				if(SuperView != null && SuperView.Subviews.Contains(this)) { 
+					Search_Changed ("");
+					SetNeedsDisplay ();
+				}
 			}
 		}
 
@@ -46,39 +46,52 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public void SetSource (IList source)
 		{
-			if (source == null)
+			if (source == null) {
 				Source = null;
-			else {
-				Source = MakeWrapper (source);
+			} else {
+				listview.SetSource (source);
+				Source = listview.Source;
 			}
 		}
 
 		/// <summary>
-		///   Changed event, raised when the selection has been confirmed.
+		/// This event is raised when the selected item in the <see cref="ComboBox"/> has changed.
 		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the selection has been confirmed.
-		/// </remarks>
-		public event EventHandler<ustring> SelectedItemChanged;
+		public Action<ListViewItemEventArgs> SelectedItemChanged;
+
+		/// <summary>
+		/// This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item.
+		/// </summary>
+		public Action<ListViewItemEventArgs> OpenSelectedItem;
 
 		IList searchset;
 		ustring text = "";
 		readonly TextField search;
 		readonly ListView listview;
-		int height;
-		int width;
 		bool autoHide = true;
 
 		/// <summary>
 		/// Public constructor
 		/// </summary>
-		public ComboBox () : base()
+		public ComboBox () : base ()
 		{
 			search = new TextField ("");
-			listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true };
+			listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false };
+
+			Initialize ();
+		}
 
+		/// <summary>
+		/// Public constructor
+		/// </summary>
+		/// <param name="text"></param>
+		public ComboBox (ustring text) : base ()
+		{
+			search = new TextField ("");
+			listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false };
+						
 			Initialize ();
+			Text = text;
 		}
 
 		/// <summary>
@@ -88,41 +101,42 @@ namespace Terminal.Gui {
 		/// <param name="source"></param>
 		public ComboBox (Rect rect, IList source) : base (rect)
 		{
-			SetSource (source);
-			this.height = rect.Height;
-			this.width = rect.Width;
-
-			search = new TextField ("") { Width = width };
-			listview = new ListView (rect, source) { LayoutStyle = LayoutStyle.Computed };
+			search = new TextField ("") { Width = rect.Width };
+			listview = new ListView (rect, source) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
 
 			Initialize ();
+			SetSource (source);
 		}
 
-		static IListDataSource MakeWrapper (IList source)
+		private void Initialize ()
 		{
-			return new ListWrapper (source);
-		}
+			search.TextChanged += Search_Changed;
 
-		private void Initialize()
-		{
-			ColorScheme = Colors.Base;
+			listview.Y = Pos.Bottom (search);
+			listview.OpenSelectedItem += (ListViewItemEventArgs a) => Selected ();
 
-			search.TextChanged += Search_Changed;
+			this.Add (search, listview);
 
 			// On resize
 			LayoutComplete += (LayoutEventArgs a) => {
-
-				search.Width = Bounds.Width;
-				listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width;
+				if (!autoHide && search.Frame.Width != Bounds.Width ||
+					autoHide && search.Frame.Width != Bounds.Width - 1) {
+					search.Width = listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width;
+					listview.Height = CalculatetHeight ();
+					search.SetRelativeLayout (Bounds);
+					listview.SetRelativeLayout (Bounds);
+				}
 			};
 
 			listview.SelectedItemChanged += (ListViewItemEventArgs e) => {
 
-				if(searchset.Count > 0)
-					SetValue ((string)searchset [listview.SelectedItem]);
+				if (searchset.Count > 0) {
+					SetValue (searchset [listview.SelectedItem]);
+				}
 			};
 
-			Application.Loaded += (Application.ResizedEventArgs a) => {
+			Added += (View v) => {
+
 				// Determine if this view is hosted inside a dialog
 				for (View view = this.SuperView; view != null; view = view.SuperView) {
 					if (view is Dialog) {
@@ -131,69 +145,95 @@ namespace Terminal.Gui {
 					}
 				}
 
-				ResetSearchSet ();
+				SetNeedsLayout ();
+				SetNeedsDisplay ();
+				Search_Changed (Text);
+			};
+		}
 
-				ColorScheme = autoHide ? Colors.Base : ColorScheme = null;
+		/// <summary>
+		/// Gets the index of the currently selected item in the <see cref="Source"/>
+		/// </summary>
+		/// <value>The selected item or -1 none selected.</value>
+		public int SelectedItem { private set; get; }
 
-				// Needs to be re-applied for LayoutStyle.Computed
-				// If Dim or Pos are null, these are the from the parametrized constructor
-				listview.Y = 1;
+		bool isShow = false;
 
-				if (Width == null) {
-					listview.Width = CalculateWidth ();
-					search.Width = width;
-				} else {
-					width = GetDimAsInt (Width, vertical: false);
-					search.Width = width;
-					listview.Width = CalculateWidth ();
-				}
+		///<inheritdoc/>
+		public new ColorScheme ColorScheme {
+			get {
+				return base.ColorScheme;
+			}
+			set {
+				listview.ColorScheme = value;
+				base.ColorScheme = value;
+				SetNeedsDisplay ();
+			}
+		}
+
+		///<inheritdoc/>
+		public override bool MouseEvent (MouseEvent me)
+		{
+			if (me.X == Bounds.Right - 1 && me.Y == Bounds.Top && me.Flags == MouseFlags.Button1Pressed
+			&& autoHide) {
 
-				if (Height == null) {
-					var h = CalculatetHeight ();
-					listview.Height = h;
-					this.Height = h + 1; // adjust view to account for search box
+				if (isShow) {
+					isShow = false;
+					HideList ();
 				} else {
-					if (height == 0)
-						height = GetDimAsInt (Height, vertical: true);
+					SetSearchSet ();
 
-					listview.Height = CalculatetHeight ();
-					this.Height = height + 1; // adjust view to account for search box
+					isShow = true;
+					ShowList ();
+					FocusSelectedItem ();
 				}
 
-				if (this.Text != null)
-					Search_Changed (Text);
-
-				if (autoHide)
-					listview.ColorScheme = Colors.Menu;
-				else
-					search.ColorScheme = Colors.Menu;
-			};
+				return true;
+			} else if (me.Flags == MouseFlags.Button1Pressed) {
+				if (!search.HasFocus) {
+					SetFocus (search);
+				}
 
-			search.MouseClick += Search_MouseClick;
+				return true;
+			}
 
-			this.Add(listview, search);
-			this.SetFocus(search);
+			return false;
 		}
 
-		private void Search_MouseClick (MouseEventArgs e)
+		private void FocusSelectedItem ()
 		{
-			if (e.MouseEvent.Flags != MouseFlags.Button1Clicked)
-				return;
-
-			SuperView.SetFocus (search);
+			listview.SelectedItem = SelectedItem > -1 ? SelectedItem : 0;
+			if (SelectedItem > -1) {
+				listview.TabStop = true;
+				this.SetFocus (listview);
+			}
 		}
 
 		///<inheritdoc/>
-		public override bool OnEnter ()
+		public override bool OnEnter (View view)
 		{
-			if (!search.HasFocus)
-				this.SetFocus (search);
+			if (!search.HasFocus && !listview.HasFocus) {
+				SetFocus (search);
+			}
 
 			search.CursorPosition = search.Text.RuneCount;
 
 			return true;
 		}
 
+		///<inheritdoc/>
+		public override bool OnLeave (View view)
+		{
+			if (autoHide && isShow && view != this && view != search && view != listview) {
+				isShow = false;
+				HideList ();
+			} else if (listview.TabStop) {
+				listview.TabStop = false;
+			}
+
+			return true;
+		}
+
 		/// <summary>
 		/// Invokes the SelectedChanged event if it is defined.
 		/// </summary>
@@ -202,46 +242,72 @@ namespace Terminal.Gui {
 		{
 			// Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. 
 			// So we cannot optimize. Ie: Don't call if not changed
-			SelectedItemChanged?.Invoke (this, search.Text);
+			SelectedItemChanged?.Invoke (new ListViewItemEventArgs(SelectedItem, search.Text));
 
 			return true;
 		}
 
-		///<inheritdoc/>
-		public override bool ProcessKey(KeyEvent e)
+		/// <summary>
+		/// Invokes the OnOpenSelectedItem event if it is defined.
+		/// </summary>
+		/// <returns></returns>
+		public virtual bool OnOpenSelectedItem ()
 		{
-			if (e.Key == Key.Tab) {
-				base.ProcessKey(e);
-				return false; // allow tab-out to next control
-			}
+			var value = search.Text;
+			OpenSelectedItem?.Invoke (new ListViewItemEventArgs (SelectedItem, value));
 
-			if (e.Key == Key.Enter && listview.HasFocus) {
-				if (listview.Source.Count == 0 || searchset.Count == 0) {
-					text = "";
-					return true;
-				}
+			return true;
+		}
 
-				SetValue((string)searchset [listview.SelectedItem]);
-				search.CursorPosition = search.Text.RuneCount;
-				Search_Changed (search.Text);
-				OnSelectedChanged ();
+		///<inheritdoc/>
+		public override void Redraw (Rect bounds)
+		{
+			base.Redraw (bounds);
+
+			if (!autoHide) {
+				return;
+			}
 
-				searchset.Clear();
-				listview.Clear ();
-				listview.Height = 0;
-				this.SetFocus(search);
+			Move (Bounds.Right - 1, 0);
+			Driver.AddRune (Driver.DownArrow);
+		}
 
+		///<inheritdoc/>
+		public override bool ProcessKey (KeyEvent e)
+		{
+			if (e.Key == Key.Enter && listview.SelectedItem > -1) {
+				Selected ();
 				return true;
 			}
 
-			if (e.Key == Key.CursorDown && search.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) { // jump to list
-				this.SetFocus (listview);
-				SetValue ((string)searchset [listview.SelectedItem]);
+			if (e.Key == Key.F4 && (search.HasFocus || listview.HasFocus)) {
+				if (!isShow) {
+					SetSearchSet ();
+					isShow = true;
+					ShowList ();
+					FocusSelectedItem ();
+				} else {
+					isShow = false;
+					HideList ();
+				}
 				return true;
 			}
 
-			if (e.Key == Key.CursorUp && search.HasFocus) // stop odd behavior on KeyUp when search has focus
+			if (e.Key == Key.CursorDown && search.HasFocus) { // jump to list
+				if (searchset.Count > 0) {
+					listview.TabStop = true;
+					this.SetFocus (listview);
+					SetValue (searchset [listview.SelectedItem]);
+					return true;
+				} else {
+					listview.TabStop = false;
+					SuperView.FocusNext ();
+				}
+			}
+
+			if (e.Key == Key.CursorUp && search.HasFocus) { // stop odd behavior on KeyUp when search has focus
 				return true;
+			}
 
 			if (e.Key == Key.CursorUp && listview.HasFocus && listview.SelectedItem == 0 && searchset.Count > 0) // jump back to search
 			{
@@ -250,6 +316,34 @@ namespace Terminal.Gui {
 				return true;
 			}
 
+			if(e.Key == Key.PageDown) {
+				if (listview.SelectedItem != -1) {
+					listview.MovePageDown ();
+				}
+				return true;
+			}
+
+			if (e.Key == Key.PageUp) {
+				if (listview.SelectedItem != -1) {
+					listview.MovePageUp ();
+				}
+				return true;
+			}
+
+			if (e.Key == Key.Home) {
+				if (listview.SelectedItem != -1) {
+					listview.MoveHome ();
+				}
+				return true;
+			}
+
+			if(e.Key == Key.End) {
+				if(listview.SelectedItem != -1) {
+					listview.MoveEnd ();
+				}
+				return true;
+			}
+
 			if (e.Key == Key.Esc) {
 				this.SetFocus (search);
 				search.Text = text = "";
@@ -258,22 +352,19 @@ namespace Terminal.Gui {
 			}
 
 			// Unix emulation
-			if (e.Key == Key.ControlU)
-			{
-				Reset();
+			if (e.Key == Key.ControlU) {
+				Reset ();
 				return true;
 			}
 
-			return base.ProcessKey(e);
+			return base.ProcessKey (e);
 		}
 
 		/// <summary>
 		/// The currently selected list item
 		/// </summary>
-		public new ustring Text
-		{
-			get
-			{
+		public new ustring Text {
+			get {
 				return text;
 			}
 			set {
@@ -281,92 +372,139 @@ namespace Terminal.Gui {
 			}
 		}
 
-		private void SetValue(ustring text)
+		private void SetValue (object text)
 		{
 			search.TextChanged -= Search_Changed;
-			this.text = search.Text = text;
+			this.text = search.Text = text.ToString();
 			search.CursorPosition = 0;
 			search.TextChanged += Search_Changed;
+			SelectedItem = GetSelectedItemFromSource (this.text);
+			OnSelectedChanged ();
+		}
+
+		private void Selected ()
+		{
+			isShow = false;
+			listview.TabStop = false;
+			if (listview.Source.Count == 0 || searchset.Count == 0) {
+				text = "";
+				return;
+			}
+
+			SetValue (searchset [listview.SelectedItem]);
+			search.CursorPosition = search.Text.RuneCount;
+			Search_Changed (search.Text);
+			OnOpenSelectedItem ();
+			Reset (keepSearchText: true);
+		}
+
+		private int GetSelectedItemFromSource (ustring value)
+		{
+			if (source == null) {
+				return -1;
+			}
+			for (int i = 0; i < source.Count; i++) {
+				if (source.ToList () [i].ToString () == value) {
+					return i;
+				}
+			}
+			return -1;
 		}
 
 		/// <summary>
 		/// Reset to full original list
 		/// </summary>
-		private void Reset()
+		private void Reset (bool keepSearchText = false)
 		{
-			search.Text = text = "";
-			OnSelectedChanged();
+			if (!keepSearchText) {
+				search.Text = text = "";
+			}
 
 			ResetSearchSet ();
 
-			listview.SetSource(searchset);
+			listview.SetSource (searchset);
 			listview.Height = CalculatetHeight ();
+			
+			this.SetFocus (search);
+		}
 
-			this.SetFocus(search);
+		private void ResetSearchSet (bool noCopy = false)
+		{
+			if (searchset == null) {
+				searchset = new List<object> ();
+			} else {
+				searchset.Clear ();
+			}
+
+			if (autoHide || noCopy)
+				return;
+			SetSearchSet ();
 		}
 
-		private void ResetSearchSet()
+		private void SetSearchSet ()
 		{
-			if (autoHide) {
-				if (searchset == null)
-					searchset = new List<string> ();
-				else
-					searchset.Clear ();
-			} else
-				searchset = source.ToList ();
+			// force deep copy
+			foreach (var item in Source.ToList ()) {
+				searchset.Add (item);
+			}
 		}
 
 		private void Search_Changed (ustring text)
 		{
-			if (source == null) // Object initialization
+			if (source == null) { // Object initialization		
 				return;
+			}
 
-			if (string.IsNullOrEmpty (search.Text.ToString ()))
+			if (ustring.IsNullOrEmpty (search.Text) && ustring.IsNullOrEmpty (text)) {
 				ResetSearchSet ();
-			else
-				searchset = source.ToList().Cast<string>().Where (x => x.StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)).ToList();
+			} else if (search.Text != text) {
+				isShow = true;
+				ResetSearchSet (noCopy: true);
 
-			listview.SetSource (searchset);
-			listview.Height = CalculatetHeight ();
+				foreach (var item in source.ToList ()) { // Iterate to preserver object type and force deep copy
+					if (item.ToString().StartsWith (search.Text.ToString(), StringComparison.CurrentCultureIgnoreCase)) { 
+						searchset.Add (item);
+					}
+				}
+			}
 
-			listview.Redraw (new Rect (0, 0, width, height)); // for any view behind this
-			this.SuperView?.BringSubviewToFront (this);
+			ShowList ();
 		}
 
 		/// <summary>
-		/// Internal height of dynamic search list
+		/// Show the search list
 		/// </summary>
-		/// <returns></returns>
-		private int CalculatetHeight ()
+		/// 
+		/// Consider making public
+		private void ShowList ()
 		{
-			return Math.Min (height, searchset.Count);
+			listview.SetSource (searchset);
+			listview.Clear (); // Ensure list shrinks in Dialog as you type
+			listview.Height = CalculatetHeight ();
+			this.SuperView?.BringSubviewToFront (this);
 		}
 
 		/// <summary>
-		/// Internal width of search list
+		/// Hide the search list
 		/// </summary>
-		/// <returns></returns>
-		private int CalculateWidth ()
+		/// 
+		/// Consider making public
+		private void HideList ()
 		{
-			return autoHide ? Math.Max (1, width - 1) : width;
+			Reset (SelectedItem > -1);
+			listview.TabStop = false;
 		}
 
 		/// <summary>
-		/// Get Dim as integer value
+		/// Internal height of dynamic search list
 		/// </summary>
-		/// <param name="dim"></param>
-		/// <param name="vertical"></param>
-		/// <returns></returns>n
-		private int GetDimAsInt (Dim dim, bool vertical)
+		/// <returns></returns>
+		private int CalculatetHeight ()
 		{
-			if (dim is Dim.DimAbsolute)
-				return dim.Anchor (0);
-			else { // Dim.Fill Dim.Factor
-				if(autoHide)
-					return vertical ? dim.Anchor (SuperView.Bounds.Height) : dim.Anchor (SuperView.Bounds.Width);
-				else 
-					return vertical ? dim.Anchor (Bounds.Height) : dim.Anchor (Bounds.Width);
-			}
+			if (Bounds.Height == 0)
+				return 0;
+
+			return Math.Min (Bounds.Height - 1, searchset?.Count > 0 ? searchset.Count : isShow ? Bounds.Height - 1 : 0);
 		}
 	}
 }

+ 15 - 0
Terminal.Gui/Views/Label.cs

@@ -55,6 +55,21 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public Action Clicked;
 
+		///// <inheritdoc/>
+		//public new ustring Text {
+		//	get => base.Text;
+		//	set {
+		//		base.Text = value;
+		//		// This supports Label auto-sizing when Text changes (preserving backwards compat behavior)
+		//		if (Frame.Height == 1 && !ustring.IsNullOrEmpty (value)) {
+		//			int w = Text.RuneCount;
+		//			Width = w;
+		//			Frame = new Rect (Frame.Location, new Size (w, Frame.Height));
+		//		}
+		//		SetNeedsDisplay ();
+		//	}
+		//}
+
 		/// <summary>
 		/// Method invoked when a mouse event is generated
 		/// </summary>

+ 64 - 2
Terminal.Gui/Views/ListView.cs

@@ -120,6 +120,7 @@ namespace Terminal.Gui {
 				source = value;
 				top = 0;
 				selected = 0;
+				lastSelectedItem = -1;
 				SetNeedsDisplay ();
 			}
 		}
@@ -205,14 +206,15 @@ namespace Terminal.Gui {
 		public int SelectedItem {
 			get => selected;
 			set {
-				if (source == null)
+				if (source == null || source.Count == 0)
 					return;
 				if (selected < 0 || selected >= source.Count)
 					throw new ArgumentException ("value");
 				selected = value;
+				OnSelectedChanged ();
 				if (selected < top)
 					top = selected;
-				else if (selected >= top + Frame.Height)
+				else if (selected >= top + (LayoutStyle == LayoutStyle.Absolute ? Frame.Height : Height.Anchor (0)))
 					top = selected;
 			}
 		}
@@ -346,6 +348,12 @@ namespace Terminal.Gui {
 				OnOpenSelectedItem ();
 				break;
 
+			case Key.End:
+				return MoveEnd ();
+
+			case Key.Home:
+				return MoveHome ();
+
 			}
 			return base.ProcessKey (kb);
 		}
@@ -459,6 +467,38 @@ namespace Terminal.Gui {
 			return true;
 		}
 
+		/// <summary>
+		/// Moves the selected item index to the last row.
+		/// </summary>
+		/// <returns></returns>
+		public virtual bool MoveEnd ()
+		{
+			if (selected != source.Count - 1) {
+				selected = source.Count - 1;
+				top = selected;
+				OnSelectedChanged ();
+				SetNeedsDisplay ();
+			}
+
+			return true;
+		}
+
+		/// <summary>
+		/// Moves the selected item index to the first row.
+		/// </summary>
+		/// <returns></returns>
+		public virtual bool MoveHome ()
+		{
+			if (selected != 0) {
+				selected = 0;
+				top = selected;
+				OnSelectedChanged ();
+				SetNeedsDisplay ();
+			}
+
+			return true;
+		}
+
 		int lastSelectedItem = -1;
 
 		/// <summary>
@@ -489,6 +529,28 @@ namespace Terminal.Gui {
 			return true;
 		}
 
+		///<inheritdoc/>
+		public override bool OnEnter (View view)
+		{
+			if (source?.Count > 0 && lastSelectedItem == -1) {
+				OnSelectedChanged ();
+				return true;
+			}
+
+			return false;
+		}
+
+		///<inheritdoc/>
+		public override bool OnMouseEnter (MouseEvent mouseEvent)
+		{
+			if (source?.Count > 0 && selected >= 0 && lastSelectedItem == -1) {
+				lastSelectedItem = selected;
+				return true;
+			}
+
+			return false;
+		}
+
 		///<inheritdoc/>
 		public override void PositionCursor ()
 		{

+ 15 - 8
Terminal.Gui/Views/Menu.cs

@@ -344,13 +344,13 @@ namespace Terminal.Gui {
 				var uncheckedChar = Driver.UnSelected;
 
 				if (item.CheckType.HasFlag (MenuItemCheckStyle.Checked)) {
-					checkChar = Driver.Checked; 
+					checkChar = Driver.Checked;
 					uncheckedChar = Driver.UnChecked;
 				}
 
 				// Support Checked even though CHeckType wasn't set
 				if (item.Checked) {
-					textToDraw = ustring.Make(new Rune [] { checkChar, ' ' }) + item.Title;
+					textToDraw = ustring.Make (new Rune [] { checkChar, ' ' }) + item.Title;
 				} else if (item.CheckType.HasFlag (MenuItemCheckStyle.Checked) ||
 					item.CheckType.HasFlag (MenuItemCheckStyle.Radio)) {
 					textToDraw = ustring.Make (new Rune [] { uncheckedChar, ' ' }) + item.Title;
@@ -535,8 +535,6 @@ namespace Terminal.Gui {
 				if (item == null || !item.IsEnabled ()) disabled = true;
 				if (item != null && !disabled)
 					current = me.Y - 1;
-				HasFocus = true;
-				SetNeedsDisplay ();
 				CheckSubMenu ();
 				return true;
 			}
@@ -795,8 +793,10 @@ namespace Terminal.Gui {
 				lastFocused = lastFocused ?? SuperView.MostFocused;
 				if (openSubMenu != null)
 					CloseMenu (false, true);
-				if (openMenu != null)
+				if (openMenu != null) {
 					SuperView.Remove (openMenu);
+					openMenu.Dispose ();
+				}
 
 				for (int i = 0; i < index; i++)
 					pos += Menus [i].Title.Length + 2;
@@ -869,11 +869,13 @@ namespace Terminal.Gui {
 			OnMenuClosing ();
 			switch (isSubMenu) {
 			case false:
-				if (openMenu != null)
+				if (openMenu != null) {
 					SuperView.Remove (openMenu);
+				}
 				SetNeedsDisplay ();
 				if (previousFocused != null && openMenu != null && previousFocused.ToString () != openCurrentMenu.ToString ())
 					previousFocused?.SuperView?.SetFocus (previousFocused);
+				openMenu?.Dispose ();
 				openMenu = null;
 				if (lastFocused is Menu) {
 					lastFocused = null;
@@ -914,8 +916,10 @@ namespace Terminal.Gui {
 				else
 					SuperView.SetFocus (openMenu);
 				if (openSubMenu != null) {
-					SuperView.Remove (openSubMenu [i]);
-					openSubMenu.Remove (openSubMenu [i]);
+					var menu = openSubMenu [i];
+					SuperView.Remove (menu);
+					openSubMenu.Remove (menu);
+					menu.Dispose ();
 				}
 				RemoveSubMenu (i);
 			}
@@ -950,6 +954,7 @@ namespace Terminal.Gui {
 			if (openSubMenu != null) {
 				foreach (var item in openSubMenu) {
 					SuperView.Remove (item);
+					item.Dispose ();
 				}
 			}
 		}
@@ -1065,6 +1070,7 @@ namespace Terminal.Gui {
 			if (mi.IsTopLevel) {
 				var menu = new Menu (this, i, 0, mi);
 				menu.Run (mi.Action);
+				menu.Dispose ();
 			} else {
 				openedByHotKey = true;
 				Application.GrabMouse (this);
@@ -1172,6 +1178,7 @@ namespace Terminal.Gui {
 							if (Menus [i].IsTopLevel) {
 								var menu = new Menu (this, i, 0, Menus [i]);
 								menu.Run (Menus [i].Action);
+								menu.Dispose ();
 							}
 						} else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked) {
 							if (IsMenuOpen) {

+ 108 - 14
Terminal.Gui/Views/ScrollView.cs

@@ -135,6 +135,10 @@ namespace Terminal.Gui {
 
 			Driver.SetAttribute (ColorScheme.Normal);
 
+			if (Bounds.Height == 0) {
+				return;
+			}
+
 			if (vertical) {
 				if (region.Right < Bounds.Width - 1)
 					return;
@@ -148,9 +152,19 @@ namespace Terminal.Gui {
 					var by2 = (position + bh) * bh / Size;
 
 					Move (col, 0);
-					Driver.AddRune (Driver.UpArrow);
-					Move (col, Bounds.Height - 1);
-					Driver.AddRune (Driver.DownArrow);
+					if (Bounds.Height == 1) {
+						Driver.AddRune (Driver.Diamond);
+					} else {
+						Driver.AddRune (Driver.UpArrow);
+					}
+					if (Bounds.Height == 3) {
+						Move (col, 1);
+						Driver.AddRune (Driver.Diamond);
+					}
+					if (Bounds.Height > 1) {
+						Move (col, Bounds.Height - 1);
+						Driver.AddRune (Driver.DownArrow);
+					}
 				} else {
 					bh -= 2;
 					var by1 = position * bh / Size;
@@ -393,6 +407,11 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
+		/// </summary>
+		public bool AutoHideScrollBars { get; set; } = true;
+
 		/// <summary>
 		/// Adds the view to the scrollview.
 		/// </summary>
@@ -494,17 +513,21 @@ namespace Terminal.Gui {
 
 			var savedClip = ClipToBounds ();
 			OnDrawContent (new Rect (ContentOffset,
-				new Size (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0),
-					Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0))));
+				new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
+					Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0), 0))));
 			contentView.Redraw (contentView.Frame);
 			Driver.Clip = savedClip;
 
-			if (ShowVerticalScrollIndicator) {
-				vertical.Redraw (vertical.Bounds);
-			}
+			if (AutoHideScrollBars) {
+				ShowHideScrollBars ();
+			} else {
+				if (ShowVerticalScrollIndicator) {
+					vertical.Redraw (vertical.Bounds);
+				}
 
-			if (ShowHorizontalScrollIndicator) {
-				horizontal.Redraw (horizontal.Bounds);
+				if (ShowHorizontalScrollIndicator) {
+					horizontal.Redraw (horizontal.Bounds);
+				}
 			}
 
 			// Fill in the bottom left corner
@@ -514,6 +537,63 @@ namespace Terminal.Gui {
 			Driver.SetAttribute (ColorScheme.Normal);
 		}
 
+		void ShowHideScrollBars ()
+		{
+			bool v = false, h = false; bool p = false;
+
+			if (Bounds.Height == 0 || Bounds.Height > contentSize.Height) {
+				if (ShowVerticalScrollIndicator) {
+					ShowVerticalScrollIndicator = false;
+				}
+				v = false;
+			} else if (Bounds.Height > 0 && Bounds.Height == contentSize.Height) {
+				p = true;
+			} else {
+				if (!ShowVerticalScrollIndicator) {
+					ShowVerticalScrollIndicator = true;
+				}
+				v = true;
+			}
+			if (Bounds.Width == 0 || Bounds.Width > contentSize.Width) {
+				if (ShowHorizontalScrollIndicator) {
+					ShowHorizontalScrollIndicator = false;
+				}
+				h = false;
+			} else if (Bounds.Width > 0 && Bounds.Width == contentSize.Width && p) {
+				if (ShowHorizontalScrollIndicator) {
+					ShowHorizontalScrollIndicator = false;
+				}
+				h = false;
+				if (ShowVerticalScrollIndicator) {
+					ShowVerticalScrollIndicator = false;
+				}
+				v = false;
+			} else {
+				if (p) {
+					if (!ShowVerticalScrollIndicator) {
+						ShowVerticalScrollIndicator = true;
+					}
+					v = true;
+				}
+				if (!ShowHorizontalScrollIndicator) {
+					ShowHorizontalScrollIndicator = true;
+				}
+				h = true;
+			}
+
+			vertical.Height = Dim.Fill (h ? 1 : 0);
+			horizontal.Width = Dim.Fill (v ? 1 : 0);
+
+			if (v) {
+				vertical.SetRelativeLayout (Bounds);
+				vertical.Redraw (vertical.Bounds);
+			}
+			if (h) {
+				horizontal.SetRelativeLayout (Bounds);
+				horizontal.Redraw (horizontal.Bounds);
+			}
+		}
+
 		void SetViewsNeedsDisplay ()
 		{
 			foreach (View view in contentView) {
@@ -631,13 +711,13 @@ namespace Terminal.Gui {
 				!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
 				return false;
 
-			if (me.Flags == MouseFlags.WheeledDown)
+			if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator)
 				ScrollDown (1);
-			else if (me.Flags == MouseFlags.WheeledUp)
+			else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator)
 				ScrollUp (1);
-			else if (me.X == vertical.Frame.X)
+			else if (me.X == vertical.Frame.X && ShowVerticalScrollIndicator)
 				vertical.MouseEvent (me);
-			else if (me.Y == horizontal.Frame.Y)
+			else if (me.Y == horizontal.Frame.Y && ShowHorizontalScrollIndicator)
 				horizontal.MouseEvent (me);
 			else if (IsOverridden (me.View)) {
 				Application.UngrabMouse ();
@@ -645,5 +725,19 @@ namespace Terminal.Gui {
 			}
 			return true;
 		}
+
+		///<inheritdoc/>
+		protected override void Dispose (bool disposing)
+		{
+			if (!showVerticalScrollIndicator) {
+				// It was not added to SuperView, so it won't get disposed automatically
+				vertical?.Dispose ();
+			}
+			if (!showHorizontalScrollIndicator) {
+				// It was not added to SuperView, so it won't get disposed automatically
+				horizontal?.Dispose ();
+			}
+			base.Dispose (disposing);
+		}
 	}
 }

+ 19 - 1
Terminal.Gui/Views/StatusBar.cs

@@ -63,6 +63,8 @@ namespace Terminal.Gui {
 	/// So for each context must be a new instance of a statusbar.
 	/// </summary>
 	public class StatusBar : View {
+		bool disposedValue;
+
 		/// <summary>
 		/// The items that compose the <see cref="StatusBar"/>
 		/// </summary>
@@ -90,7 +92,12 @@ namespace Terminal.Gui {
 			Width = Dim.Fill ();
 			Height = 1;
 
-			LayoutComplete += (e) => {
+			Application.Resized += Application_Resized ();
+		}
+
+		private Action<Application.ResizedEventArgs> Application_Resized ()
+		{
+			return delegate {
 				X = 0;
 				Height = 1;
 				if (SuperView == null || SuperView == Application.Top) {
@@ -192,5 +199,16 @@ namespace Terminal.Gui {
 				return false;
 			});
 		}
+
+		/// <inheritdoc/>
+		protected override void Dispose (bool disposing)
+		{
+			if (!disposedValue) {
+				if (disposing) {
+					Application.Resized -= Application_Resized ();
+				}
+				disposedValue = true;
+			}
+		}
 	}
 }

+ 3 - 3
Terminal.Gui/Views/TextField.cs

@@ -33,7 +33,7 @@ namespace Terminal.Gui {
 		public bool ReadOnly { get; set; } = false;
 
 		/// <summary>
-		///   Changed event, raised when the text has clicked.
+		///   Changed event, raised when the text has changed.
 		/// </summary>
 		/// <remarks>
 		///   This event is raised when the <see cref="Text"/> changes. 
@@ -93,14 +93,14 @@ namespace Terminal.Gui {
 		}
 
 		///<inheritdoc/>
-		public override bool OnLeave ()
+		public override bool OnLeave (View view)
 		{
 			if (Application.mouseGrabView != null && Application.mouseGrabView == this)
 				Application.UngrabMouse ();
 			if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar))
 				ClearAllSelection ();
 
-			return base.OnLeave ();
+			return base.OnLeave (view);
 		}
 
 		///<inheritdoc/>

+ 2 - 2
Terminal.Gui/Views/TimeField.cs

@@ -77,10 +77,10 @@ namespace Terminal.Gui {
 			shortFormat = $" hh\\{sepChar}mm";
 			CursorPosition = 1;
 			Time = time;
-			TextChanged += TimeField_Changed;
+			TextChanged += TextField_TextChanged;
 		}
 
-		void TimeField_Changed (ustring e)
+		void TextField_TextChanged (ustring e)
 		{
 			try {
 				if (!TimeSpan.TryParseExact (Text.ToString ().Trim (), Format.Trim (), CultureInfo.CurrentCulture, TimeSpanStyles.None, out TimeSpan result))

+ 11 - 1
Terminal.Gui/Windows/FileDialog.cs

@@ -439,7 +439,11 @@ namespace Terminal.Gui {
 		/// <param name="message">The message.</param>
 		public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message) : base (title)//, Driver.Cols - 20, Driver.Rows - 5, null)
 		{
-			this.message = new Label (Rect.Empty, "MESSAGE" + message);
+			this.message = new Label (message) { 
+				X = 1,
+				Y = 0,
+			};
+			Add (this.message);
 			var msgLines = TextFormatter.MaxLines (message, Driver.Cols - 20);
 
 			dirLabel = new Label ("Directory: ") {
@@ -509,6 +513,12 @@ namespace Terminal.Gui {
 			//SetFocus (nameEntry);
 		}
 
+		//protected override void Dispose (bool disposing)
+		//{
+		//	message?.Dispose ();
+		//	base.Dispose (disposing);
+		//}
+
 		/// <summary>
 		/// Gets or sets the prompt label for the <see cref="Button"/> displayed to the user
 		/// </summary>

+ 14 - 3
UICatalog/Scenarios/AllViewsTester.cs

@@ -2,6 +2,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Reflection;
 using System.Text;
@@ -334,7 +335,9 @@ namespace UICatalog {
 		{
 			// Remove existing class, if any
 			if (view != null) {
+				view.LayoutComplete -= LayoutCompleteHandler;
 				_hostPane.Remove (view);
+				view.Dispose ();
 				_hostPane.Clear ();
 			}
 		}
@@ -346,8 +349,8 @@ namespace UICatalog {
 
 			//_curView.X = Pos.Center ();
 			//_curView.Y = Pos.Center ();
-			//_curView.Width = Dim.Fill (5);
-			//_curView.Height = Dim.Fill (5);
+			view.Width = Dim.Percent(75);
+			view.Height = Dim.Percent (75);
 
 			// Set the colorscheme to make it stand out
 			view.ColorScheme = Colors.Base;
@@ -369,7 +372,7 @@ namespace UICatalog {
 
 			// If the view supports a Source property, set it so we have something to look at
 			if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType().GetProperty("Source").PropertyType == typeof(Terminal.Gui.IListDataSource)) {
-				var source = new ListWrapper (new List<ustring> () { ustring.Make ("List Item #1"), ustring.Make ("List Item #2"), ustring.Make ("List Item #3")});
+				var source = new ListWrapper (new List<ustring> () { ustring.Make ("Test Text #1"), ustring.Make ("Test Text #2"), ustring.Make ("Test Text #3") });
 				view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
 			}
 
@@ -384,9 +387,17 @@ namespace UICatalog {
 			_hostPane.SetNeedsDisplay ();
 			UpdateSettings (view);
 			UpdateTitle (view);
+
+			view.LayoutComplete += LayoutCompleteHandler;
+
 			return view;
 		}
 
+		void LayoutCompleteHandler(View.LayoutEventArgs args)
+		{
+			UpdateTitle (_curView);
+		}
+
 		public override void Run ()
 		{
 			base.Run ();

+ 10 - 5
UICatalog/Scenarios/CharacterMap.cs

@@ -16,9 +16,10 @@ namespace UICatalog {
 	[ScenarioCategory ("Text")]
 	[ScenarioCategory ("Controls")]
 	class CharacterMap : Scenario {
+		CharMap _charMap;
 		public override void Setup ()
 		{
-			var charMap = new CharMap () {
+			_charMap = new CharMap () {
 				X = 0,
 				Y = 0,
 				Width = CharMap.RowWidth + 2,
@@ -28,8 +29,8 @@ namespace UICatalog {
 				CanFocus = true,
 			};
 
-			Win.Add (charMap);
-			var label = new Label ("Jump To Unicode Block:") { X = Pos.Right (charMap) + 1, Y = Pos.Y (charMap) };
+			Win.Add (_charMap);
+			var label = new Label ("Jump To Unicode Block:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
 			Win.Add (label);
 
 			(ustring radioLabel, int start, int end) CreateRadio (ustring title, int start, int end)
@@ -62,11 +63,16 @@ namespace UICatalog {
 			jumpList.Y = Pos.Bottom (label);
 			jumpList.Width = Dim.Fill ();
 			jumpList.SelectedItemChanged = (args) => {
-				charMap.Start = radioItems [args.SelectedItem].start;
+				_charMap.Start = radioItems[args.SelectedItem].start;
 			};
 
 			Win.Add (jumpList);
 		}
+
+		public override void Run ()
+		{
+			base.Run ();
+		}
 	}
 
 	class CharMap : ScrollView {
@@ -106,7 +112,6 @@ namespace UICatalog {
 
 			DrawContent += CharMap_DrawContent;
 		}
-
 #if true
 		private void CharMap_DrawContent (Rect viewport)
 		{

+ 10 - 9
UICatalog/Scenarios/ListsAndCombos.cs

@@ -12,13 +12,14 @@ namespace UICatalog.Scenarios {
 
 		public override void Setup ()
 		{
-			List<string> items = new List<string> ();
-			foreach (var dir in new [] { "/etc", @"\windows\System32" }) {
+			//TODO: Duplicated code in Demo.cs Consider moving to shared assembly
+			var items = new List<ustring> ();
+			foreach (var dir in new [] { "/etc", @$"{Environment.GetEnvironmentVariable ("SystemRoot")}\System32" }) {
 				if (Directory.Exists (dir)) {
 					items = Directory.GetFiles (dir).Union(Directory.GetDirectories(dir))
-					.Select (Path.GetFileName)
-					.Where (x => char.IsLetterOrDigit (x [0]))
-					.OrderBy (x => x).ToList ();
+						.Select (Path.GetFileName)
+						.Where (x => char.IsLetterOrDigit (x [0]))
+						.OrderBy (x => x).Select(x => ustring.Make(x)).ToList() ;
 				}
 			}
 
@@ -26,16 +27,16 @@ namespace UICatalog.Scenarios {
 			var lbListView = new Label ("Listview") {
 				ColorScheme = Colors.TopLevel,
 				X = 0,
-				Width = 30
+				Width = Dim.Percent (40)
 			};
 
 			var listview = new ListView (items) {
 				X = 0,
 				Y = Pos.Bottom (lbListView) + 1,
 				Height = Dim.Fill(2),
-				Width = 30
+				Width = Dim.Percent (40)
 			};
-			listview.OpenSelectedItem += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
+			listview.SelectedItemChanged += (ListViewItemEventArgs e) => lbListView.Text = items [listview.SelectedItem];
 			Win.Add (lbListView, listview);
 
 			// ComboBox
@@ -53,7 +54,7 @@ namespace UICatalog.Scenarios {
 			};
 			comboBox.SetSource (items);
 
-			comboBox.SelectedItemChanged += (object sender, ustring text) => lbComboBox.Text = text;
+			comboBox.SelectedItemChanged += (ListViewItemEventArgs text) => lbComboBox.Text = items[comboBox.SelectedItem];
 			Win.Add (lbComboBox, comboBox);
 		}
 	}

+ 78 - 23
UICatalog/Scenarios/Text.cs

@@ -9,51 +9,106 @@ namespace UICatalog {
 	class Text : Scenario {
 		public override void Setup ()
 		{
-			var s = "This is a test intended to show how TAB key works (or doesn't) across text fields.";
-			Win.Add (new TextField (s) {
-				X = 5,
+			var s = "TAB to jump between text fields.";
+			var textField = new TextField (s) {
+				X = 1,
 				Y = 1,
-				Width = Dim.Percent (80),
-				ColorScheme = Colors.Dialog
-			});
+				Width = Dim.Percent (50),
+				//ColorScheme = Colors.Dialog
+			};
+			Win.Add (textField);
+
+			var labelMirroringTextField = new Label (textField.Text) {
+				X = Pos.Right (textField) + 1,
+				Y = Pos.Top (textField),
+				Width = Dim.Fill (1)
+			};
+			Win.Add (labelMirroringTextField);
+
+			textField.TextChanged += (prev) => {
+				labelMirroringTextField.Text = textField.Text;
+			};
 
 			var textView = new TextView () {
-				X = 5,
+				X = 1,
 				Y = 3,
-				Width = Dim.Percent (80),
-				Height = Dim.Percent (40),
+				Width = Dim.Percent (50),
+				Height = Dim.Percent (30),
 				ColorScheme = Colors.Dialog
 			};
 			textView.Text = s;
 			Win.Add (textView);
 
+			var labelMirroringTextView = new Label (textView.Text) {
+				X = Pos.Right (textView) + 1,
+				Y = Pos.Top (textView),
+				Width = Dim.Fill (1),
+				Height = Dim.Height (textView),
+			};
+			Win.Add (labelMirroringTextView);
+
+			textView.TextChanged += () => {
+				labelMirroringTextView.Text = textView.Text;
+			};
+
 			// BUGBUG: 531 - TAB doesn't go to next control from HexView
-			var hexView = new HexView (new System.IO.MemoryStream(Encoding.ASCII.GetBytes (s))) {
-				X = 5,
-				Y = Pos.Bottom(textView) + 1,
-				Width = Dim.Percent(80),
-				Height = Dim.Percent(40),
-				ColorScheme = Colors.Dialog
+			var hexView = new HexView (new System.IO.MemoryStream (Encoding.ASCII.GetBytes (s))) {
+				X = 1,
+				Y = Pos.Bottom (textView) + 1,
+				Width = Dim.Fill (1),
+				Height = Dim.Percent (30),
+				//ColorScheme = Colors.Dialog
 			};
 			Win.Add (hexView);
 
 			var dateField = new DateField (System.DateTime.Now) {
-				X = 5,
+				X = 1,
 				Y = Pos.Bottom (hexView) + 1,
-				Width = Dim.Percent (40),
-				ColorScheme = Colors.Dialog,
+				Width = 20,
+				//ColorScheme = Colors.Dialog,
 				IsShortFormat = false
 			};
 			Win.Add (dateField);
 
-			var timeField = new TimeField (DateTime.Now.TimeOfDay) {
-				X = Pos.Right (dateField) + 5,
+			var labelMirroringDateField = new Label (dateField.Text) {
+				X = Pos.Right (dateField) + 1,
+				Y = Pos.Top (dateField),
+				Width = Dim.Width (dateField),
+				Height = Dim.Height (dateField),
+			};
+			Win.Add (labelMirroringDateField);
+
+			dateField.TextChanged += (prev) => {
+				labelMirroringDateField.Text = dateField.Text;
+			};
+
+			_timeField = new TimeField (DateTime.Now.TimeOfDay) {
+				X = Pos.Right (labelMirroringDateField) + 5,
 				Y = Pos.Bottom (hexView) + 1,
-				Width = Dim.Percent (40),
-				ColorScheme = Colors.Dialog,
+				Width = 20,
+				//ColorScheme = Colors.Dialog,
 				IsShortFormat = false
 			};
-			Win.Add (timeField);
+			Win.Add (_timeField);
+
+			_labelMirroringTimeField = new Label (_timeField.Text) {
+				X = Pos.Right (_timeField) + 1,
+				Y = Pos.Top (_timeField),
+				Width = Dim.Width (_timeField),
+				Height = Dim.Height (_timeField),
+			};
+			Win.Add (_labelMirroringTimeField);
+
+			_timeField.TimeChanged += TimeChanged;
+
+		}
+
+		TimeField _timeField;
+		Label _labelMirroringTimeField;
+
+		private void TimeChanged (DateTimeEventArgs<TimeSpan> e)
+		{
+			_labelMirroringTimeField.Text = _timeField.Text;
 
 		}
 	}

+ 18 - 6
UICatalog/Scenarios/TimeAndDate.cs

@@ -53,37 +53,49 @@ namespace UICatalog {
 
 			lblOldTime = new Label ("Old Time: ") {
 				X = Pos.Center (),
-				Y = Pos.Bottom (longDate) + 1
+				Y = Pos.Bottom (longDate) + 1,
+				TextAlignment = TextAlignment.Centered,
+				Width = Dim.Fill(),
 			};
 			Win.Add (lblOldTime);
 
 			lblNewTime = new Label ("New Time: ") {
 				X = Pos.Center (),
-				Y = Pos.Bottom (lblOldTime) + 1
+				Y = Pos.Bottom (lblOldTime) + 1,
+				TextAlignment = TextAlignment.Centered,
+				Width = Dim.Fill (),
 			};
 			Win.Add (lblNewTime);
 
 			lblTimeFmt = new Label ("Time Format: ") {
 				X = Pos.Center (),
-				Y = Pos.Bottom (lblNewTime) + 1
+				Y = Pos.Bottom (lblNewTime) + 1,
+				TextAlignment = TextAlignment.Centered,
+				Width = Dim.Fill (),
 			};
 			Win.Add (lblTimeFmt);
 
 			lblOldDate = new Label ("Old Date: ") {
 				X = Pos.Center (),
-				Y = Pos.Bottom (lblTimeFmt) + 2
+				Y = Pos.Bottom (lblTimeFmt) + 2,
+				TextAlignment = TextAlignment.Centered,
+				Width = Dim.Fill (),
 			};
 			Win.Add (lblOldDate);
 
 			lblNewDate = new Label ("New Date: ") {
 				X = Pos.Center (),
-				Y = Pos.Bottom (lblOldDate) + 1
+				Y = Pos.Bottom (lblOldDate) + 1,
+				TextAlignment = TextAlignment.Centered,
+				Width = Dim.Fill (),
 			};
 			Win.Add (lblNewDate);
 
 			lblDateFmt = new Label ("Date Format: ") {
 				X = Pos.Center (),
-				Y = Pos.Bottom (lblNewDate) + 1
+				Y = Pos.Bottom (lblNewDate) + 1,
+				TextAlignment = TextAlignment.Centered,
+				Width = Dim.Fill (),
 			};
 			Win.Add (lblDateFmt);
 

+ 2 - 0
UICatalog/Scenarios/TopLevelNoWindowBug.cs

@@ -8,6 +8,8 @@ namespace UICatalog {
 
 		public override void Run ()
 		{
+			Top?.Dispose ();
+
 			Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
 
 			var menu = new MenuBar (new MenuBarItem [] {

+ 3 - 1
UICatalog/Scenarios/Unicode.cs

@@ -56,6 +56,8 @@ namespace UICatalog {
 			var checkBox = new CheckBox (gitString) { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
 			Win.Add (checkBox);
 
+			// BUGBUG: Combobox does not deal with unicode properly. 
+#if false
 			label = new Label ("ComboBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
 			Win.Add (label);
 			var comboBox = new ComboBox () {
@@ -67,7 +69,7 @@ namespace UICatalog {
 
 			Win.Add (comboBox);
 			comboBox.Text = gitString;
-
+#endif
 			label = new Label ("HexView:") { X = Pos.X (label), Y = Pos.Bottom (label) + 2 };
 			Win.Add (label);
 			var hexView = new HexView (new System.IO.MemoryStream (Encoding.ASCII.GetBytes (gitString + " Со_хранить"))) {

+ 111 - 100
UICatalog/UICatalog.cs

@@ -78,17 +78,42 @@ namespace UICatalog {
 				return;
 			}
 
-			Scenario scenario = GetScenarioToRun ();
-			while (scenario != null) {
+			Scenario scenario;
+			while ((scenario = GetScenarioToRun ()) != null) {
+#if DEBUG_IDISPOSABLE
+				// Validate there are no outstanding Responder-based instances 
+				// after a sceanario was selected to run. This proves the main UI Catalog
+				// 'app' closed cleanly.
+				foreach (var inst in Responder.Instances) {
+					Debug.Assert (inst.WasDisposed);
+				}
+				Responder.Instances.Clear ();
+#endif
+
 				Application.UseSystemConsole = _useSystemConsole;
 				Application.Init ();
 				scenario.Init (Application.Top, _baseColorScheme);
 				scenario.Setup ();
 				scenario.Run ();
-				scenario = GetScenarioToRun ();
+
+#if DEBUG_IDISPOSABLE
+				// After the scenario runs, validate all Responder-based instances
+				// were disposed. This proves the scenario 'app' closed cleanly.
+				foreach (var inst in Responder.Instances) {
+					Debug.Assert (inst.WasDisposed);
+				}
+				Responder.Instances.Clear();
+#endif
+			}
+
+#if DEBUG_IDISPOSABLE
+			// This proves that when the user exited the UI Catalog app
+			// it cleaned up properly.
+			foreach (var inst in Responder.Instances) {
+				Debug.Assert (inst.WasDisposed);
 			}
-			if (!_top.Running)
-				Application.Shutdown (true);
+			Responder.Instances.Clear ();
+#endif
 		}
 
 		/// <summary>
@@ -100,101 +125,6 @@ namespace UICatalog {
 			Application.UseSystemConsole = false;
 			Application.Init ();
 
-			if (_menu == null) {
-				Setup ();
-			}
-
-			_top = Application.Top;
-
-			_top.KeyDown += KeyDownHandler;
-
-			_top.Add (_menu);
-			_top.Add (_leftPane);
-			_top.Add (_rightPane);
-			_top.Add (_statusBar);
-
-			_top.Ready += () => {
-				if (_runningScenario != null) {
-					_top.SetFocus (_rightPane);
-					_runningScenario = null;
-				}
-			};
-
-			Application.Run (_top, false);
-			Application.Shutdown (false);
-			return _runningScenario;
-		}
-
-		static MenuItem [] CreateDiagnosticMenuItems ()
-		{
-			MenuItem CheckedMenuMenuItem (ustring menuItem, Action action, Func<bool> checkFunction)
-			{
-				var mi = new MenuItem ();
-				mi.Title = menuItem;
-				mi.CheckType |= MenuItemCheckStyle.Checked;
-				mi.Checked = checkFunction ();
-				mi.Action = () => {
-					action?.Invoke ();
-					mi.Title = menuItem;
-					mi.Checked = checkFunction ();
-				};
-				return mi;
-			}
-
-			return new MenuItem [] {
-				CheckedMenuMenuItem ("Use _System Console",
-					() => {
-						_useSystemConsole = !_useSystemConsole;
-					},
-					() => _useSystemConsole),
-				CheckedMenuMenuItem ("Diagnostics: _Frame Padding",
-					() => {
-						ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
-						_top.SetNeedsDisplay ();
-					},
-					() => (ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding),
-				CheckedMenuMenuItem ("Diagnostics: Frame _Ruler",
-					() => {
-						ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
-						_top.SetNeedsDisplay ();
-					},
-					() => (ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler),
-			};
-		}
-
-		static void SetColorScheme ()
-		{
-			_leftPane.ColorScheme = _baseColorScheme;
-			_rightPane.ColorScheme = _baseColorScheme;
-			_top?.SetNeedsDisplay ();
-		}
-
-		static ColorScheme _baseColorScheme;
-		static MenuItem [] CreateColorSchemeMenuItems ()
-		{
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			foreach (var sc in Colors.ColorSchemes) {
-				var item = new MenuItem ();
-				item.Title = sc.Key;
-				item.CheckType |= MenuItemCheckStyle.Radio;
-				item.Checked = sc.Value == _baseColorScheme;
-				item.Action += () => {
-					_baseColorScheme = sc.Value;
-					SetColorScheme ();
-					foreach (var menuItem in menuItems) {
-						menuItem.Checked = menuItem.Title.Equals (sc.Key) && sc.Value == _baseColorScheme;
-					}
-				};
-				menuItems.Add (item);
-			}
-			return menuItems.ToArray ();
-		}
-
-		/// <summary>
-		/// Create all controls. This gets called once and the controls remain with their state between Sceanrio runs.
-		/// </summary>
-		private static void Setup ()
-		{
 			// Set this here because not initilzied until driver is loaded
 			_baseColorScheme = Colors.Base;
 
@@ -284,6 +214,87 @@ namespace UICatalog {
 			});
 
 			SetColorScheme ();
+			_top = Application.Top;
+			_top.KeyDown += KeyDownHandler;
+			_top.Add (_menu);
+			_top.Add (_leftPane);
+			_top.Add (_rightPane);
+			_top.Add (_statusBar);
+			_top.Ready += () => {
+				if (_runningScenario != null) {
+					_top.SetFocus (_rightPane);
+					_runningScenario = null;
+				}
+			};
+
+			Application.Run (_top, true);
+			Application.Shutdown ();
+			return _runningScenario;
+		}
+
+		static MenuItem [] CreateDiagnosticMenuItems ()
+		{
+			MenuItem CheckedMenuMenuItem (ustring menuItem, Action action, Func<bool> checkFunction)
+			{
+				var mi = new MenuItem ();
+				mi.Title = menuItem;
+				mi.CheckType |= MenuItemCheckStyle.Checked;
+				mi.Checked = checkFunction ();
+				mi.Action = () => {
+					action?.Invoke ();
+					mi.Title = menuItem;
+					mi.Checked = checkFunction ();
+				};
+				return mi;
+			}
+
+			return new MenuItem [] {
+				CheckedMenuMenuItem ("Use _System Console",
+					() => {
+						_useSystemConsole = !_useSystemConsole;
+					},
+					() => _useSystemConsole),
+				CheckedMenuMenuItem ("Diagnostics: _Frame Padding",
+					() => {
+						ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
+						_top.SetNeedsDisplay ();
+					},
+					() => (ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding),
+				CheckedMenuMenuItem ("Diagnostics: Frame _Ruler",
+					() => {
+						ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
+						_top.SetNeedsDisplay ();
+					},
+					() => (ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler),
+			};
+		}
+
+		static void SetColorScheme ()
+		{
+			_leftPane.ColorScheme = _baseColorScheme;
+			_rightPane.ColorScheme = _baseColorScheme;
+			_top?.SetNeedsDisplay ();
+		}
+
+		static ColorScheme _baseColorScheme;
+		static MenuItem [] CreateColorSchemeMenuItems ()
+		{
+			List<MenuItem> menuItems = new List<MenuItem> ();
+			foreach (var sc in Colors.ColorSchemes) {
+				var item = new MenuItem ();
+				item.Title = sc.Key;
+				item.CheckType |= MenuItemCheckStyle.Radio;
+				item.Checked = sc.Value == _baseColorScheme;
+				item.Action += () => {
+					_baseColorScheme = sc.Value;
+					SetColorScheme ();
+					foreach (var menuItem in menuItems) {
+						menuItem.Checked = menuItem.Title.Equals (sc.Key) && sc.Value == _baseColorScheme;
+					}
+				};
+				menuItems.Add (item);
+			}
+			return menuItems.ToArray ();
 		}
 
 		private static void _scenarioListView_OpenSelectedItem (EventArgs e)

+ 8 - 0
UICatalog/UICatalog.csproj

@@ -8,6 +8,14 @@
     <LangVersion>8.0</LangVersion>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
+  </PropertyGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
   </ItemGroup>

+ 7 - 0
UnitTests/ApplicationTests.cs

@@ -12,6 +12,13 @@ using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui {
 	public class ApplicationTests {
+		public ApplicationTests ()
+		{
+#if DEBUG_IDISPOSABLE
+			Responder.Instances.Clear ();
+#endif
+		}
+
 		[Fact]
 		public void Init_Shutdown_Cleans_Up ()
 		{

+ 4 - 4
UnitTests/MainLoopTests.cs

@@ -289,9 +289,9 @@ namespace Terminal.Gui {
 			var token = ml.AddTimeout (ms, callback);
 			watch.Start ();
 			ml.Run ();
-			// +/- 10ms should be good enuf
+			// +/- 100ms should be good enuf
 			// https://github.com/xunit/assert.xunit/pull/25
-			Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (10));
+			Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
 			ml.RemoveTimeout (token);
 			Assert.Equal (1, callbackCount);
@@ -317,9 +317,9 @@ namespace Terminal.Gui {
 			var token = ml.AddTimeout (ms, callback);
 			watch.Start ();
 			ml.Run ();
-			// +/- 10ms should be good enuf
+			// +/- 100ms should be good enuf
 			// https://github.com/xunit/assert.xunit/pull/25
-			Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (10));
+			Assert.Equal<TimeSpan> (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
 			ml.RemoveTimeout (token);
 			Assert.Equal (2, callbackCount);

+ 9 - 2
UnitTests/ResponderTests.cs

@@ -32,8 +32,15 @@ namespace Terminal.Gui {
 			Assert.False (r.MouseEvent (new MouseEvent () { Flags = MouseFlags.AllEvents }));
 			Assert.False (r.OnMouseEnter (new MouseEvent () { Flags = MouseFlags.AllEvents }));
 			Assert.False (r.OnMouseLeave (new MouseEvent () { Flags = MouseFlags.AllEvents }));
-			Assert.False (r.OnEnter ());
-			Assert.False (r.OnLeave ());
+			Assert.False (r.OnEnter (new View ()));
+			Assert.False (r.OnLeave (new View ()));
+		}
+
+		// Generic lifetime (IDisposable) tests
+		[Fact]
+		public void Dispose_Works ()
+		{
+
 		}
 	}
 }

+ 20 - 0
UnitTests/ScenarioTests.cs

@@ -10,6 +10,13 @@ using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui {
 	public class ScenarioTests {
+		public ScenarioTests ()
+		{
+#if DEBUG_IDISPOSABLE
+			Responder.Instances.Clear ();
+#endif
+		}
+
 		int CreateInput (string input)
 		{
 			// Put a control-q in at the end
@@ -71,6 +78,12 @@ namespace Terminal.Gui {
 				Assert.Equal (1, iterations);
 				Assert.Equal (stackSize, iterations);
 			}
+#if DEBUG_IDISPOSABLE
+			foreach (var inst in Responder.Instances) {
+				Assert.True (inst.WasDisposed);
+			}
+			Responder.Instances.Clear ();
+#endif
 		}
 
 		[Fact]
@@ -119,6 +132,13 @@ namespace Terminal.Gui {
 			// # of key up events should match # of iterations
 			//Assert.Equal (1, iterations);
 			Assert.Equal (stackSize, iterations);
+
+#if DEBUG_IDISPOSABLE
+			foreach (var inst in Responder.Instances) {
+				Assert.True (inst.WasDisposed);
+			}
+			Responder.Instances.Clear ();
+#endif
 		}
 	}
 }

+ 66 - 0
UnitTests/TextFormatterTests.cs

@@ -16,10 +16,76 @@ namespace Terminal.Gui {
 		[Fact]
 		public void Basic_Usage ()
 		{
+			var testText = ustring.Make("test");
+			var expectedSize = new Size ();
+			var testBounds = new Rect (0, 0, 100, 1);
 			var tf = new TextFormatter ();
 
+			tf.Text = testText;
+			expectedSize = new Size (testText.Length, 1);
+			Assert.Equal (testText, tf.Text);
+			Assert.Equal (TextAlignment.Left, tf.Alignment);
+			Assert.Equal (expectedSize, tf.Size);
+			tf.Draw (testBounds, new Attribute(), new Attribute());
+			Assert.Equal (expectedSize, tf.Size);
+			Assert.NotEmpty (tf.Lines);
+
+			tf.Alignment = TextAlignment.Right;
+			expectedSize = new Size (testText.Length, 1);
+			Assert.Equal (testText, tf.Text);
+			Assert.Equal (TextAlignment.Right, tf.Alignment);
+			Assert.Equal (expectedSize, tf.Size);
+			tf.Draw (testBounds, new Attribute (), new Attribute ());
+			Assert.Equal (expectedSize, tf.Size);
+			Assert.NotEmpty (tf.Lines);
+
+			tf.Alignment = TextAlignment.Right;
+			expectedSize = new Size (testText.Length * 2, 1);
+			tf.Size = expectedSize;
+			Assert.Equal (testText, tf.Text);
+			Assert.Equal (TextAlignment.Right, tf.Alignment);
+			Assert.Equal (expectedSize, tf.Size);
+			tf.Draw (testBounds, new Attribute (), new Attribute ());
+			Assert.Equal (expectedSize, tf.Size);
+			Assert.NotEmpty (tf.Lines);
+
+			tf.Alignment = TextAlignment.Centered;
+			expectedSize = new Size (testText.Length * 2, 1);
+			tf.Size = expectedSize;
+			Assert.Equal (testText, tf.Text);
+			Assert.Equal (TextAlignment.Centered, tf.Alignment);
+			Assert.Equal (expectedSize, tf.Size);
+			tf.Draw (testBounds, new Attribute (), new Attribute ());
+			Assert.Equal (expectedSize, tf.Size);
+			Assert.NotEmpty (tf.Lines);
+		}
+
+		[Fact]
+		public void NeedsFormat_Sets ()
+		{
+			var testText = ustring.Make ("test");
+			var testBounds = new Rect (0, 0, 100, 1);
+			var tf = new TextFormatter ();
 
+			tf.Text = "test";
+			Assert.True (tf.NeedsFormat); // get_Lines causes a Format
+			Assert.NotEmpty (tf.Lines);
+			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+			Assert.Equal (testText, tf.Text);
+			tf.Draw (testBounds, new Attribute (), new Attribute ());
+			Assert.False (tf.NeedsFormat);
+
+			tf.Size = new Size (1, 1);
+			Assert.True (tf.NeedsFormat); 
+			Assert.NotEmpty (tf.Lines);
+			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+
+			tf.Alignment = TextAlignment.Centered;
+			Assert.True (tf.NeedsFormat);
+			Assert.NotEmpty (tf.Lines);
+			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
 		}
+
 		[Fact]
 		public void FindHotKey_Invalid_ReturnsFalse ()
 		{

+ 8 - 0
UnitTests/UnitTests.csproj

@@ -5,6 +5,14 @@
     <IsPackable>false</IsPackable>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
+  </PropertyGroup>
+
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
     <PackageReference Include="System.Collections" Version="4.3.0" />

+ 410 - 2
UnitTests/ViewTests.cs

@@ -102,8 +102,8 @@ namespace Terminal.Gui {
 			Assert.False (r.MouseEvent (new MouseEvent () { Flags = MouseFlags.AllEvents }));
 			Assert.False (r.OnMouseEnter (new MouseEvent () { Flags = MouseFlags.AllEvents }));
 			Assert.False (r.OnMouseLeave (new MouseEvent () { Flags = MouseFlags.AllEvents }));
-			Assert.False (r.OnEnter ());
-			Assert.False (r.OnLeave ());
+			Assert.False (r.OnEnter (new View ()));
+			Assert.False (r.OnLeave (new View ()));
 
 			// TODO: Add more
 		}
@@ -135,5 +135,413 @@ namespace Terminal.Gui {
 			sub2.Width = Dim.Width (sub2);
 			Assert.Throws<InvalidOperationException> (() => root.LayoutSubviews ());
 		}
+
+		[Fact]
+		public void Added_Removed ()
+		{
+			var v = new View (new Rect (0, 0, 10, 24));
+			var t = new View ();
+
+			v.Added += (View e) => {
+				Assert.True (v.SuperView == e);
+			};
+
+			v.Removed += (View e) => {
+				Assert.True (v.SuperView == null);
+			};
+
+			t.Add (v);
+			Assert.True (t.Subviews.Count == 1);
+
+			t.Remove (v);
+			Assert.True (t.Subviews.Count == 0);
+		}
+
+		[Fact]
+		public void Subviews_TabIndexes_AreEqual ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			Assert.True (r.Subviews.IndexOf (v1) == 0);
+			Assert.True (r.Subviews.IndexOf (v2) == 1);
+			Assert.True (r.Subviews.IndexOf (v3) == 2);
+
+			Assert.True (r.TabIndexes.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v2) == 1);
+			Assert.True (r.TabIndexes.IndexOf (v3) == 2);
+
+			Assert.Equal (r.Subviews.IndexOf (v1), r.TabIndexes.IndexOf (v1));
+			Assert.Equal (r.Subviews.IndexOf (v2), r.TabIndexes.IndexOf (v2));
+			Assert.Equal (r.Subviews.IndexOf (v3), r.TabIndexes.IndexOf (v3));
+		}
+
+		[Fact]
+		public void BringSubviewToFront_Subviews_vs_TabIndexes ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			r.BringSubviewToFront (v1);
+			Assert.True (r.Subviews.IndexOf (v1) == 2);
+			Assert.True (r.Subviews.IndexOf (v2) == 0);
+			Assert.True (r.Subviews.IndexOf (v3) == 1);
+
+			Assert.True (r.TabIndexes.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v2) == 1);
+			Assert.True (r.TabIndexes.IndexOf (v3) == 2);
+		}
+
+		[Fact]
+		public void BringSubviewForward_Subviews_vs_TabIndexes ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			r.BringSubviewForward (v1);
+			Assert.True (r.Subviews.IndexOf (v1) == 1);
+			Assert.True (r.Subviews.IndexOf (v2) == 0);
+			Assert.True (r.Subviews.IndexOf (v3) == 2);
+
+			Assert.True (r.TabIndexes.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v2) == 1);
+			Assert.True (r.TabIndexes.IndexOf (v3) == 2);
+		}
+
+		[Fact]
+		public void SendSubviewToBack_Subviews_vs_TabIndexes ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			r.SendSubviewToBack (v3);
+			Assert.True (r.Subviews.IndexOf (v1) == 1);
+			Assert.True (r.Subviews.IndexOf (v2) == 2);
+			Assert.True (r.Subviews.IndexOf (v3) == 0);
+
+			Assert.True (r.TabIndexes.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v2) == 1);
+			Assert.True (r.TabIndexes.IndexOf (v3) == 2);
+		}
+
+		[Fact]
+		public void SendSubviewBackwards_Subviews_vs_TabIndexes ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			r.SendSubviewBackwards (v3);
+			Assert.True (r.Subviews.IndexOf (v1) == 0);
+			Assert.True (r.Subviews.IndexOf (v2) == 2);
+			Assert.True (r.Subviews.IndexOf (v3) == 1);
+
+			Assert.True (r.TabIndexes.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v2) == 1);
+			Assert.True (r.TabIndexes.IndexOf (v3) == 2);
+		}
+
+		[Fact]
+		public void TabIndex_Set_CanFocus_ValidValues ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			v1.TabIndex = 1;
+			Assert.True (r.Subviews.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v1) == 1);
+
+			v1.TabIndex = 2;
+			Assert.True (r.Subviews.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v1) == 2);
+		}
+
+		[Fact]
+		public void TabIndex_Set_CanFocus_HigherValues ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			v1.TabIndex = 3;
+			Assert.True (r.Subviews.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v1) == 2);
+		}
+
+		[Fact]
+		public void TabIndex_Set_CanFocus_LowerValues ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			v1.TabIndex = -1;
+			Assert.True (r.Subviews.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v1) == 0);
+		}
+
+		[Fact]
+		public void TabIndex_Set_CanFocus_False ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			v1.CanFocus = false;
+			v1.TabIndex = 0;
+			Assert.True (r.Subviews.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v1) == 0);
+			Assert.Equal (-1, v1.TabIndex);
+		}
+
+		[Fact]
+		public void TabIndex_Set_CanFocus_False_To_True ()
+		{
+			var r = new View ();
+			var v1 = new View ();
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			v1.CanFocus = true;
+			v1.TabIndex = 1;
+			Assert.True (r.Subviews.IndexOf (v1) == 0);
+			Assert.True (r.TabIndexes.IndexOf (v1) == 1);
+		}
+
+		[Fact]
+		public void TabStop_And_CanFocus_Are_All_True ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true };
+			var v2 = new View () { CanFocus = true };
+			var v3 = new View () { CanFocus = true };
+
+			r.Add (v1, v2, v3);
+
+			r.FocusNext ();
+			Assert.True (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.True (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.True (v3.HasFocus);
+		}
+
+		[Fact]
+		public void TabStop_Are_All_True_And_CanFocus_Are_All_False ()
+		{
+			var r = new View ();
+			var v1 = new View ();
+			var v2 = new View ();
+			var v3 = new View ();
+
+			r.Add (v1, v2, v3);
+
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+		}
+
+		[Fact]
+		public void TabStop_Are_All_False_And_CanFocus_Are_All_True ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true, TabStop = false };
+			var v2 = new View () { CanFocus = true, TabStop = false };
+			var v3 = new View () { CanFocus = true, TabStop = false };
+
+			r.Add (v1, v2, v3);
+
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+		}
+
+		[Fact]
+		public void TabStop_And_CanFocus_Mixed_And_BothFalse ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true, TabStop = false };
+			var v2 = new View () { CanFocus = false, TabStop = true };
+			var v3 = new View () { CanFocus = false, TabStop = false };
+
+			r.Add (v1, v2, v3);
+
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+		}
+
+		[Fact]
+		public void TabStop_All_True_And_Changing_CanFocus_Later ()
+		{
+			var r = new View ();
+			var v1 = new View ();
+			var v2 = new View ();
+			var v3 = new View ();
+
+			r.Add (v1, v2, v3);
+
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+
+			v1.CanFocus = true;
+			r.FocusNext ();
+			Assert.True (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			v2.CanFocus = true;
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.True (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			v3.CanFocus = true;
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.True (v3.HasFocus);
+		}
+
+		[Fact]
+		public void TabStop_All_False_And_All_True_And_Changing_TabStop_Later ()
+		{
+			var r = new View ();
+			var v1 = new View () { CanFocus = true, TabStop = false };
+			var v2 = new View () { CanFocus = true, TabStop = false };
+			var v3 = new View () { CanFocus = true, TabStop = false };
+
+			r.Add (v1, v2, v3);
+
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+
+			v1.TabStop = true;
+			r.FocusNext ();
+			Assert.True (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			v2.TabStop = true;
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.True (v2.HasFocus);
+			Assert.False (v3.HasFocus);
+			v3.TabStop = true;
+			r.FocusNext ();
+			Assert.False (v1.HasFocus);
+			Assert.False (v2.HasFocus);
+			Assert.True (v3.HasFocus);
+		}
+
+		[Fact]
+		public void CanFocus_Set_Changes_TabIndex_And_TabStop ()
+		{
+			var r = new View ();
+			var v1 = new View ("1");
+			var v2 = new View ("2");
+			var v3 = new View ("3");
+
+			r.Add (v1, v2, v3);
+
+			v2.CanFocus = true;
+			Assert.Equal (r.TabIndexes.IndexOf (v2), v2.TabIndex);
+			Assert.Equal (0, v2.TabIndex);
+			Assert.True (v2.TabStop);
+
+			v1.CanFocus = true;
+			Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex);
+			Assert.Equal (1, v1.TabIndex);
+			Assert.True (v1.TabStop);
+
+			v1.TabIndex = 2;
+			Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex);
+			Assert.Equal (1, v1.TabIndex);
+			v3.CanFocus = true;
+			Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex);
+			Assert.Equal (1, v1.TabIndex);
+			Assert.Equal (r.TabIndexes.IndexOf (v3), v3.TabIndex);
+			Assert.Equal (2, v3.TabIndex);
+			Assert.True (v3.TabStop);
+
+			v2.CanFocus = false;
+			Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex);
+			Assert.Equal (1, v1.TabIndex);
+			Assert.True (v1.TabStop);
+			Assert.NotEqual (r.TabIndexes.IndexOf (v2), v2.TabIndex);
+			Assert.Equal (-1, v2.TabIndex);
+			Assert.False (v2.TabStop);
+			Assert.Equal (r.TabIndexes.IndexOf (v3), v3.TabIndex);
+			Assert.Equal (2, v3.TabIndex);
+			Assert.True (v3.TabStop);
+		}
 	}
 }