diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index 95d3dadad..47324606a 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -59,7 +59,7 @@ namespace Terminal.Gui { /// The current in use. /// public static ConsoleDriver Driver; - + /// /// The object used for the application on startup () /// @@ -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 { /// public static void Run (Func 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; + } } } diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index a933f9a05..a6bc51955 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -112,7 +112,7 @@ namespace Terminal.Gui { void Initialize () { - ColorScheme = Colors.Base; + ColorScheme = Colors.TopLevel; } /// @@ -142,12 +142,12 @@ namespace Terminal.Gui { /// /// Gets or sets the menu for this Toplevel /// - public MenuBar MenuBar { get; set; } + public virtual MenuBar MenuBar { get; set; } /// /// Gets or sets the status bar for this Toplevel /// - public StatusBar StatusBar { get; set; } + public virtual StatusBar StatusBar { get; set; } /// 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 { /// 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) + /// + /// Virtual method which allow to be overridden to implement specific positions for inherited . + /// + /// The toplevel. + 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 { /// 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 (); diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index 4cea78d2f..ee790f088 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/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) { diff --git a/UICatalog/Scenarios/BackgroundWorkerSample.cs b/UICatalog/Scenarios/BackgroundWorkerSample.cs new file mode 100644 index 000000000..330a36e8b --- /dev/null +++ b/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 (); + + Top.Dispose (); + } + } + + public class MainApp : Toplevel { + private List log = new List (); + private ListView listLog; + private Dictionary 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 (); + 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); + } + 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 (); + } + 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 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); + } + } +}