Toplevel improvement as a subviews container without frame borders.

This commit is contained in:
BDisp
2021-06-05 00:47:03 +01:00
parent bbffac8f23
commit 480a63d222
4 changed files with 276 additions and 26 deletions

View File

@@ -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;
}
}
}

View File

@@ -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 ();

View File

@@ -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) {

View File

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