Browse Source

Toplevel improvement as a subviews container without frame borders.

BDisp 4 years ago
parent
commit
480a63d222

+ 38 - 8
Terminal.Gui/Core/Application.cs

@@ -59,7 +59,7 @@ namespace Terminal.Gui {
 		/// The current <see cref="ConsoleDriver"/> in use.
 		/// </summary>
 		public static ConsoleDriver Driver;
-		
+
 		/// <summary>
 		/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
 		/// </summary>
@@ -508,6 +508,7 @@ namespace Terminal.Gui {
 			}
 			toplevels.Push (toplevel);
 			Current = toplevel;
+			SetCurrentAsTop ();
 			Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent);
 			if (toplevel.LayoutStyle == LayoutStyle.Computed)
 				toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows));
@@ -544,9 +545,10 @@ namespace Terminal.Gui {
 
 		// Encapsulate all setting of initial state for Application; Having
 		// this in a function like this ensures we don't make mistakes in
-		// guranteeing that the state of this singleton is deterministic when Init
+		// guaranteeing that the state of this singleton is deterministic when Init
 		// starts running and after Shutdown returns.
-		static void ResetState () {
+		static void ResetState ()
+		{
 			// Shutdown is the bookend for Init. As such it needs to clean up all resources
 			// Init created. Apps that do any threading will need to code defensively for this.
 			// e.g. see Issue #537
@@ -613,6 +615,7 @@ namespace Terminal.Gui {
 				Current = null;
 			} else {
 				Current = toplevels.Peek ();
+				SetCurrentAsTop ();
 				Refresh ();
 			}
 		}
@@ -644,7 +647,7 @@ namespace Terminal.Gui {
 
 					MainLoop.MainIteration ();
 					Iteration?.Invoke ();
-					
+
 					if (Driver.EnsureCursorVisibility ()) {
 						state.Toplevel.SetNeedsDisplay ();
 					}
@@ -692,7 +695,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static void Run<T> (Func<Exception, bool> errorHandler = null) where T : Toplevel, new()
 		{
-			Init (() => new T ());
+			if (_initialized && Driver != null) {
+				var top = new T ();
+				if (top.GetType ().BaseType == typeof (Toplevel)) {
+					Top = top;
+				} else {
+					throw new ArgumentException (top.GetType ().BaseType.Name);
+				}
+			} else {
+				Init (() => new T ());
+			}
 			Run (Top, errorHandler);
 		}
 
@@ -788,9 +800,7 @@ namespace Terminal.Gui {
 		static void TerminalResized ()
 		{
 			var full = new Rect (0, 0, Driver.Cols, Driver.Rows);
-			Top.Frame = full;
-			Top.Width = full.Width;
-			Top.Height = full.Height;
+			SetToplevelsSize (full);
 			Resized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height });
 			Driver.Clip = full;
 			foreach (var t in toplevels) {
@@ -800,5 +810,25 @@ namespace Terminal.Gui {
 			}
 			Refresh ();
 		}
+
+		static void SetToplevelsSize (Rect full)
+		{
+			foreach (var t in toplevels) {
+				if (t?.SuperView == null && !t.Modal) {
+					t.Frame = full;
+					t.Width = full.Width;
+					t.Height = full.Height;
+				}
+			}
+		}
+
+		static bool SetCurrentAsTop ()
+		{
+			if (Current != Top && Current?.SuperView == null && !Current.Modal) {
+				Top = Current;
+				return true;
+			}
+			return false;
+		}
 	}
 }

+ 26 - 16
Terminal.Gui/Core/Toplevel.cs

@@ -112,7 +112,7 @@ namespace Terminal.Gui {
 
 		void Initialize ()
 		{
-			ColorScheme = Colors.Base;
+			ColorScheme = Colors.TopLevel;
 		}
 
 		/// <summary>
@@ -142,12 +142,12 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Gets or sets the menu for this Toplevel
 		/// </summary>
-		public MenuBar MenuBar { get; set; }
+		public virtual MenuBar MenuBar { get; set; }
 
 		/// <summary>
 		/// Gets or sets the status bar for this Toplevel
 		/// </summary>
-		public StatusBar StatusBar { get; set; }
+		public virtual StatusBar StatusBar { get; set; }
 
 		///<inheritdoc/>
 		public override bool OnKeyDown (KeyEvent keyEvent)
@@ -234,7 +234,7 @@ namespace Terminal.Gui {
 					old?.SetNeedsDisplay ();
 					Focused?.SetNeedsDisplay ();
 				} else {
-					FocusNearestView (SuperView?.TabIndexes?.Reverse(), Direction.Backward);
+					FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward);
 				}
 				return true;
 			case Key.Tab | Key.CtrlMask:
@@ -265,7 +265,7 @@ namespace Terminal.Gui {
 				return true;
 			}
 
-			if (ShortcutHelper.FindAndOpenByShortcut(keyEvent, this)) {
+			if (ShortcutHelper.FindAndOpenByShortcut (keyEvent, this)) {
 				return true;
 			}
 			return false;
@@ -319,9 +319,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void Add (View view)
 		{
-			if (this == Application.Top) {
-				AddMenuStatusBar (view);
-			}
+			AddMenuStatusBar (view);
 			base.Add (view);
 		}
 
@@ -424,10 +422,14 @@ namespace Terminal.Gui {
 			}
 		}
 
-		private void PositionToplevel (Toplevel top)
+		/// <summary>
+		/// Virtual method which allow to be overridden to implement specific positions for inherited <see cref="Toplevel"/>.
+		/// </summary>
+		/// <param name="top">The toplevel.</param>
+		public virtual void PositionToplevel (Toplevel top)
 		{
 			EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
-			if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
+			if (top?.SuperView != null && (nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
 				if ((top.X == null || top.X is Pos.PosAbsolute) && top.Bounds.X != nx) {
 					top.X = nx;
 				}
@@ -435,11 +437,18 @@ namespace Terminal.Gui {
 					top.Y = ny;
 				}
 			}
-			if (top.StatusBar != null) {
-				if (ny + top.Frame.Height > top.Frame.Height - (top.StatusBar.Visible ? 1 : 0)) {
-					if (top.Height is Dim.DimFill)
-						top.Height = Dim.Fill () - (top.StatusBar.Visible ? 1 : 0);
+			var statusBar = top?.SuperView != null && top.SuperView is Toplevel toplevel
+				? toplevel.StatusBar : null;
+
+			if (statusBar != null) {
+				if (ny + top.Frame.Height != top.SuperView.Frame.Height - (statusBar.Visible ? 1 : 0)) {
+					if (top.Height is Dim.DimFill) {
+						top.Height = Dim.Fill (statusBar.Visible ? 1 : 0);
+					}
 				}
+				top.SuperView.LayoutSubviews ();
+			}
+			if (top.StatusBar != null) {
 				if (top.StatusBar.Frame.Y != top.Frame.Height - (top.StatusBar.Visible ? 1 : 0)) {
 					top.StatusBar.Y = top.Frame.Height - (top.StatusBar.Visible ? 1 : 0);
 					top.LayoutSubviews ();
@@ -451,13 +460,14 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		{
-			if (IsCurrentTop || this == Application.Top) {
+			if (IsCurrentTop || this == Application.Top || Application.Current.GetType ().BaseType == typeof (Toplevel)) {
 				if (!NeedDisplay.IsEmpty || LayoutNeeded) {
 					Driver.SetAttribute (Colors.TopLevel.Normal);
 
 					// This is the Application.Top. Clear just the region we're being asked to redraw 
 					// (the bounds passed to us).
-					Clear (bounds);
+					// Must be the screen-relative region to clear, not the bounds.
+					Clear (Frame);
 					Driver.SetAttribute (Colors.Base.Normal);
 					PositionToplevels ();
 

+ 4 - 2
UICatalog/Scenarios/AllViewsTester.cs

@@ -366,8 +366,10 @@ namespace UICatalog {
 			view.Width = Dim.Percent(75);
 			view.Height = Dim.Percent (75);
 
-			// Set the colorscheme to make it stand out
-			view.ColorScheme = Colors.Base;
+			// Set the colorscheme to make it stand out if is null by default
+			if (view.ColorScheme == null) {
+				view.ColorScheme = Colors.Base;
+			}
 
 			// If the view supports a Text property, set it so we have something to look at
 			if (view.GetType ().GetProperty ("Text") != null) {

+ 208 - 0
UICatalog/Scenarios/BackgroundWorkerSample.cs

@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Threading;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "BackgroundWorker", Description: "A persisting multi Toplevel BackgroundWorker threading")]
+	[ScenarioCategory ("Threading")]
+	[ScenarioCategory ("TopLevel")]
+	[ScenarioCategory ("Dialogs")]
+	[ScenarioCategory ("Controls")]
+	class BackgroundWorkerSample : Scenario {
+		public override void Run ()
+		{
+			Top.Dispose ();
+
+			Application.Run<MainApp> ();
+
+			Top.Dispose ();
+		}
+	}
+
+	public class MainApp : Toplevel {
+		private List<string> log = new List<string> ();
+		private ListView listLog;
+		private Dictionary<StagingUIController, BackgroundWorker> stagingWorkers;
+
+		public MainApp ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_Options", new MenuItem [] {
+					new MenuItem ("_Run Worker", "", () => RunWorker(), null, null, Key.CtrlMask | Key.R),
+					new MenuItem ("_Cancel Worker", "", () => CancelWorker(), null, null, Key.CtrlMask | Key.C),
+					null,
+					new MenuItem ("_Quit", "", () => Application.RequestStop (), null, null, Key.CtrlMask | Key.Q)
+				})
+			});
+			Add (menu);
+
+			var statusBar = new StatusBar (new [] {
+				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Exit", () => Application.RequestStop()),
+				new StatusItem(Key.CtrlMask | Key.P, "~^R~ Run Worker", () => RunWorker()),
+				new StatusItem(Key.CtrlMask | Key.P, "~^C~ Cancel Worker", () => CancelWorker())
+			});
+			Add (statusBar);
+
+			var top = new Toplevel ();
+
+			top.Add (new Label ("Worker Log") {
+				X = Pos.Center (),
+				Y = 0
+			});
+
+			listLog = new ListView (log) {
+				X = 0,
+				Y = 2,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			top.Add (listLog);
+			Add (top);
+		}
+
+		private void RunWorker ()
+		{
+			var stagingUI = new StagingUIController ();
+
+			var worker = new BackgroundWorker () { WorkerSupportsCancellation = true };
+
+			worker.DoWork += (s, e) => {
+				var stageResult = new List<string> ();
+				for (int i = 0; i < 500; i++) {
+					stageResult.Add (
+						$"Worker {i} started at {DateTime.UtcNow}");
+					e.Result = stageResult;
+					Thread.Sleep (1);
+					if (worker.CancellationPending) {
+						e.Cancel = true;
+						return;
+					}
+				}
+			};
+
+			worker.RunWorkerCompleted += (s, e) => {
+				if (e.Error != null) {
+					// Failed
+					log.Add ($"Exception occurred {e.Error.Message} on Worker {stagingUI.StartStaging}.{stagingUI.StartStaging:fff} at {DateTime.UtcNow}");
+					listLog.SetNeedsDisplay ();
+				} else if (e.Cancelled) {
+					// Canceled
+					log.Add ($"Worker {stagingUI.StartStaging}.{stagingUI.StartStaging:fff} was canceled at {DateTime.UtcNow}!");
+					listLog.SetNeedsDisplay ();
+				} else {
+					// Passed
+					log.Add ($"Worker {stagingUI.StartStaging}.{stagingUI.StartStaging:fff} was completed at {DateTime.UtcNow}.");
+					listLog.SetNeedsDisplay ();
+					Application.Refresh ();
+					stagingUI.Load (e.Result as List<string>);
+				}
+				stagingWorkers.Remove (stagingUI);
+			};
+
+			Application.Run (stagingUI);
+
+			if (stagingUI.StartStaging != null) {
+				log.Add ($"Worker is started at {stagingUI.StartStaging}.{stagingUI.StartStaging:fff}");
+				listLog.SetNeedsDisplay ();
+				if (stagingWorkers == null) {
+					stagingWorkers = new Dictionary<StagingUIController, BackgroundWorker> ();
+				}
+				stagingWorkers.Add (stagingUI, worker);
+				worker.RunWorkerAsync ();
+			}
+		}
+
+		private void CancelWorker ()
+		{
+			if (stagingWorkers.Count == 0) {
+				log.Add ($"Worker is not running at {DateTime.UtcNow}!");
+				listLog.SetNeedsDisplay ();
+				return;
+			}
+
+			var eStaging = stagingWorkers.GetEnumerator ();
+			eStaging.MoveNext ();
+			var fStaging = eStaging.Current;
+			var stagingUI = fStaging.Key;
+			var worker = fStaging.Value;
+			worker.CancelAsync ();
+			log.Add ($"Worker {stagingUI.StartStaging}.{stagingUI.StartStaging:fff} is canceling at {DateTime.UtcNow}!");
+			listLog.SetNeedsDisplay ();
+		}
+	}
+
+	public class StagingUIController : Window {
+		private Label label;
+		private ListView listView;
+		private Button start;
+		private Button close;
+
+		public DateTime? StartStaging { get; private set; }
+
+		public StagingUIController ()
+		{
+			X = Pos.Center ();
+			Y = Pos.Center ();
+			Width = Dim.Percent (85);
+			Height = Dim.Percent (85);
+
+			ColorScheme = Colors.Dialog;
+			Modal = true;
+
+			Title = "Run Worker";
+
+			label = new Label ("Press start to do the work or close to exit.") {
+				X = Pos.Center (),
+				Y = 1,
+				ColorScheme = Colors.Dialog
+			};
+			Add (label);
+
+			listView = new ListView () {
+				X = 0,
+				Y = 2,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (2)
+			};
+			Add (listView);
+
+			start = new Button ("Start") { IsDefault = true };
+			start.Clicked += () => {
+				StartStaging = DateTime.UtcNow;
+				Application.RequestStop ();
+			};
+			Add (start);
+
+			close = new Button ("Close");
+			close.Clicked += () => Application.RequestStop ();
+			Add (close);
+
+			LayoutStarted += (_) => {
+				var btnsWidth = start.Bounds.Width + close.Bounds.Width + 2 - 1;
+				var shiftLeft = Math.Max ((Bounds.Width - btnsWidth) / 2 - 2, 0);
+
+				shiftLeft += close.Bounds.Width + 1;
+				close.X = Pos.AnchorEnd (shiftLeft);
+				close.Y = Pos.AnchorEnd (1);
+
+				shiftLeft += start.Bounds.Width + 1;
+				start.X = Pos.AnchorEnd (shiftLeft);
+				start.Y = Pos.AnchorEnd (1);
+			};
+
+		}
+
+		public void Load (List<string> list)
+		{
+			var stagingUI = new StagingUIController ();
+			stagingUI.Title = $"Worker started at {StartStaging}.{StartStaging:fff}";
+			stagingUI.label.Text = "Work list:";
+			stagingUI.listView.SetSource (list);
+			stagingUI.start.Visible = false;
+
+			Application.Run (stagingUI);
+		}
+	}
+}