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