mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Toplevel improvement as a subviews container without frame borders.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
|
||||
@@ -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
UICatalog/Scenarios/BackgroundWorkerSample.cs
Normal file
208
UICatalog/Scenarios/BackgroundWorkerSample.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user