mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Added a Mdi Container feature.
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
// - "Colors" type or "Attributes" type?
|
||||
// - What to surface as "BackgroundCOlor" when clearing a window, an attribute or colors?
|
||||
//
|
||||
// Optimziations
|
||||
// Optimizations
|
||||
// - Add rendering limitation to the exposed area
|
||||
using System;
|
||||
using System.Collections;
|
||||
@@ -23,7 +23,7 @@ using System.ComponentModel;
|
||||
namespace Terminal.Gui {
|
||||
|
||||
/// <summary>
|
||||
/// A static, singelton class provding the main application driver for Terminal.Gui apps.
|
||||
/// A static, singleton class providing the main application driver for Terminal.Gui apps.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
@@ -55,11 +55,36 @@ namespace Terminal.Gui {
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class Application {
|
||||
static Stack<Toplevel> toplevels = new Stack<Toplevel> ();
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="ConsoleDriver"/> in use.
|
||||
/// </summary>
|
||||
public static ConsoleDriver Driver;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the Mdi childes which represent all the not modal <see cref="Toplevel"/> from the <see cref="MdiTop"/>.
|
||||
/// </summary>
|
||||
public static List<Toplevel> MdiChildes {
|
||||
get {
|
||||
if (MdiTop != null) {
|
||||
List<Toplevel> mdiChildes = new List<Toplevel> ();
|
||||
foreach (var top in toplevels) {
|
||||
if (top != MdiTop && !top.Modal) {
|
||||
mdiChildes.Add (top);
|
||||
}
|
||||
}
|
||||
return mdiChildes;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Toplevel"/> object used for the application on startup which <see cref="Toplevel.IsMdiContainer"/> is true.
|
||||
/// </summary>
|
||||
public static Toplevel MdiTop { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
|
||||
/// </summary>
|
||||
@@ -125,8 +150,6 @@ namespace Terminal.Gui {
|
||||
/// <value>The main loop.</value>
|
||||
public static MainLoop MainLoop { get; private set; }
|
||||
|
||||
static Stack<Toplevel> toplevels = new Stack<Toplevel> ();
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised on each iteration of the <see cref="MainLoop"/>
|
||||
/// </summary>
|
||||
@@ -252,6 +275,9 @@ namespace Terminal.Gui {
|
||||
SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
|
||||
}
|
||||
Top = topLevelFactory ();
|
||||
if (Top.IsMdiContainer) {
|
||||
MdiTop = Top;
|
||||
}
|
||||
Current = Top;
|
||||
_initialized = true;
|
||||
}
|
||||
@@ -349,6 +375,72 @@ namespace Terminal.Gui {
|
||||
}
|
||||
}
|
||||
|
||||
static View FindDeepestTop (Toplevel start, int x, int y, out int resx, out int resy)
|
||||
{
|
||||
var startFrame = start.Frame;
|
||||
|
||||
if (!startFrame.Contains (x, y)) {
|
||||
resx = 0;
|
||||
resy = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (toplevels != null) {
|
||||
int count = toplevels.Count;
|
||||
if (count > 0) {
|
||||
var rx = x - startFrame.X;
|
||||
var ry = y - startFrame.Y;
|
||||
foreach (var t in toplevels) {
|
||||
if (t != Current) {
|
||||
if (t != start && t.Visible && t.Frame.Contains (rx, ry)) {
|
||||
start = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resx = x - startFrame.X;
|
||||
resy = y - startFrame.Y;
|
||||
return start;
|
||||
}
|
||||
|
||||
static View FindDeepestMdiView (View start, int x, int y, out int resx, out int resy)
|
||||
{
|
||||
if (start.GetType ().BaseType != typeof (Toplevel)
|
||||
&& !((Toplevel)start).IsMdiContainer) {
|
||||
resx = 0;
|
||||
resy = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
var startFrame = start.Frame;
|
||||
|
||||
if (!startFrame.Contains (x, y)) {
|
||||
resx = 0;
|
||||
resy = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
int count = toplevels.Count;
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
foreach (var top in toplevels) {
|
||||
var rx = x - startFrame.X;
|
||||
var ry = y - startFrame.Y;
|
||||
if (top.Visible && top.Frame.Contains (rx, ry)) {
|
||||
var deep = FindDeepestView (top, rx, ry, out resx, out resy);
|
||||
if (deep == null)
|
||||
return FindDeepestMdiView (top, rx, ry, out resx, out resy);
|
||||
if (deep != MdiTop)
|
||||
return deep;
|
||||
}
|
||||
}
|
||||
}
|
||||
resx = x - startFrame.X;
|
||||
resy = y - startFrame.Y;
|
||||
return start;
|
||||
}
|
||||
|
||||
static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
|
||||
{
|
||||
var startFrame = start.Frame;
|
||||
@@ -380,6 +472,18 @@ namespace Terminal.Gui {
|
||||
return start;
|
||||
}
|
||||
|
||||
static View FindTopFromView (View view)
|
||||
{
|
||||
View top = view?.SuperView != null ? view.SuperView : view;
|
||||
|
||||
while (top?.SuperView != null) {
|
||||
if (top?.SuperView != null) {
|
||||
top = top.SuperView;
|
||||
}
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
internal static View mouseGrabView;
|
||||
|
||||
/// <summary>
|
||||
@@ -441,6 +545,17 @@ namespace Terminal.Gui {
|
||||
}
|
||||
}
|
||||
|
||||
if ((view == null || view == MdiTop) && !Current.Modal && MdiTop != null
|
||||
&& me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) {
|
||||
|
||||
var top = FindDeepestTop (Top, me.X, me.Y, out _, out _);
|
||||
view = FindDeepestView (top, me.X, me.Y, out rx, out ry);
|
||||
|
||||
if (view != null && view != MdiTop && top != Current) {
|
||||
MoveCurrent ((Toplevel)top);
|
||||
}
|
||||
}
|
||||
|
||||
if (view != null) {
|
||||
var nme = new MouseEvent () {
|
||||
X = rx,
|
||||
@@ -473,6 +588,56 @@ namespace Terminal.Gui {
|
||||
}
|
||||
}
|
||||
|
||||
// Only return true if the Current has changed.
|
||||
static bool MoveCurrent (Toplevel top)
|
||||
{
|
||||
// The Current is modal and the top is not modal toplevel then
|
||||
// the Current must be moved above the first not modal toplevel.
|
||||
if (MdiTop != null && top != MdiTop && top != Current && Current?.Modal == true && !toplevels.Peek ().Modal) {
|
||||
lock (toplevels) {
|
||||
toplevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
|
||||
}
|
||||
var index = 0;
|
||||
var savedToplevels = toplevels.ToArray ();
|
||||
foreach (var t in savedToplevels) {
|
||||
if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) {
|
||||
lock (toplevels) {
|
||||
toplevels.MoveTo (top, index, new ToplevelEqualityComparer ());
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// The Current and the top are both not running toplevel then
|
||||
// the top must be moved above the first not running toplevel.
|
||||
if (MdiTop != null && top != MdiTop && top != Current && Current?.Running == false && !top.Running) {
|
||||
lock (toplevels) {
|
||||
toplevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
|
||||
}
|
||||
var index = 0;
|
||||
foreach (var t in toplevels.ToArray ()) {
|
||||
if (!t.Running && t != Current && index > 0) {
|
||||
lock (toplevels) {
|
||||
toplevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ((MdiTop != null && top?.Modal == true && toplevels.Peek () != top)
|
||||
|| (MdiTop != null && Current != MdiTop && Current?.Modal == false && top == MdiTop)
|
||||
|| (MdiTop != null && Current?.Modal == false && top != Current)
|
||||
|| (MdiTop != null && Current?.Modal == true && top == MdiTop)) {
|
||||
lock (toplevels) {
|
||||
toplevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
|
||||
Current = top;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool OutsideFrame (Point p, Rect r)
|
||||
{
|
||||
return p.X < 0 || p.X > r.Width - 1 || p.Y < 0 || p.Y > r.Height - 1;
|
||||
@@ -493,8 +658,12 @@ namespace Terminal.Gui {
|
||||
/// </remarks>
|
||||
public static RunState Begin (Toplevel toplevel)
|
||||
{
|
||||
if (toplevel == null)
|
||||
if (toplevel == null) {
|
||||
throw new ArgumentNullException (nameof (toplevel));
|
||||
} else if (toplevel.IsMdiContainer && MdiTop != null) {
|
||||
throw new InvalidOperationException ("Only one Mdi Container is allowed.");
|
||||
}
|
||||
|
||||
var rs = new RunState (toplevel);
|
||||
|
||||
Init ();
|
||||
@@ -506,18 +675,68 @@ namespace Terminal.Gui {
|
||||
initializable.BeginInit ();
|
||||
initializable.EndInit ();
|
||||
}
|
||||
toplevels.Push (toplevel);
|
||||
Current = toplevel;
|
||||
SetCurrentAsTop ();
|
||||
|
||||
lock (toplevels) {
|
||||
if (string.IsNullOrEmpty (toplevel.Id.ToString ())) {
|
||||
var count = 1;
|
||||
var id = (toplevels.Count + count).ToString ();
|
||||
while (toplevels.Count > 0 && toplevels.FirstOrDefault (x => x.Id.ToString () == id) != null) {
|
||||
count++;
|
||||
id = (toplevels.Count + count).ToString ();
|
||||
}
|
||||
toplevel.Id = (toplevels.Count + count).ToString ();
|
||||
|
||||
toplevels.Push (toplevel);
|
||||
} else {
|
||||
var dup = toplevels.FirstOrDefault (x => x.Id.ToString () == toplevel.Id);
|
||||
if (dup == null) {
|
||||
toplevels.Push (toplevel);
|
||||
}
|
||||
}
|
||||
|
||||
if (toplevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) {
|
||||
throw new ArgumentException ("There are duplicates toplevels Id's");
|
||||
}
|
||||
}
|
||||
if (toplevel.IsMdiContainer) {
|
||||
MdiTop = toplevel;
|
||||
Top = MdiTop;
|
||||
}
|
||||
|
||||
var refreshDriver = true;
|
||||
if (MdiTop == null || toplevel.IsMdiContainer || (Current?.Modal == false && toplevel.Modal)
|
||||
|| (Current?.Modal == false && !toplevel.Modal) || (Current?.Modal == true && toplevel.Modal)) {
|
||||
|
||||
if (toplevel.Visible) {
|
||||
Current = toplevel;
|
||||
SetCurrentAsTop ();
|
||||
} else {
|
||||
refreshDriver = false;
|
||||
}
|
||||
} else if ((MdiTop != null && toplevel != MdiTop && Current?.Modal == true && !toplevels.Peek ().Modal)
|
||||
|| (MdiTop != null && toplevel != MdiTop && Current?.Running == false)) {
|
||||
refreshDriver = false;
|
||||
MoveCurrent (toplevel);
|
||||
} else {
|
||||
refreshDriver = false;
|
||||
MoveCurrent (Current);
|
||||
}
|
||||
|
||||
Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent);
|
||||
if (toplevel.LayoutStyle == LayoutStyle.Computed)
|
||||
toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows));
|
||||
toplevel.PositionToplevels ();
|
||||
toplevel.LayoutSubviews ();
|
||||
toplevel.WillPresent ();
|
||||
toplevel.OnLoaded ();
|
||||
Redraw (toplevel);
|
||||
toplevel.PositionCursor ();
|
||||
Driver.Refresh ();
|
||||
if (refreshDriver) {
|
||||
if (MdiTop != null) {
|
||||
MdiTop.OnChildLoaded (toplevel);
|
||||
}
|
||||
toplevel.OnLoaded ();
|
||||
Redraw (toplevel);
|
||||
toplevel.PositionCursor ();
|
||||
Driver.Refresh ();
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
@@ -531,7 +750,11 @@ namespace Terminal.Gui {
|
||||
if (runState == null)
|
||||
throw new ArgumentNullException (nameof (runState));
|
||||
|
||||
runState.Toplevel.OnUnloaded ();
|
||||
if (MdiTop != null) {
|
||||
MdiTop.OnChildUnloaded (runState.Toplevel);
|
||||
} else {
|
||||
runState.Toplevel.OnUnloaded ();
|
||||
}
|
||||
runState.Dispose ();
|
||||
}
|
||||
|
||||
@@ -560,6 +783,7 @@ namespace Terminal.Gui {
|
||||
toplevels.Clear ();
|
||||
Current = null;
|
||||
Top = null;
|
||||
MdiTop = null;
|
||||
|
||||
MainLoop = null;
|
||||
Driver?.End ();
|
||||
@@ -597,8 +821,10 @@ namespace Terminal.Gui {
|
||||
Driver.UpdateScreen ();
|
||||
View last = null;
|
||||
foreach (var v in toplevels.Reverse ()) {
|
||||
v.SetNeedsDisplay ();
|
||||
v.Redraw (v.Bounds);
|
||||
if (v.Visible) {
|
||||
v.SetNeedsDisplay ();
|
||||
v.Redraw (v.Bounds);
|
||||
}
|
||||
last = v;
|
||||
}
|
||||
last?.PositionCursor ();
|
||||
@@ -611,6 +837,19 @@ namespace Terminal.Gui {
|
||||
throw new ArgumentException ("The view that you end with must be balanced");
|
||||
toplevels.Pop ();
|
||||
|
||||
(view as Toplevel)?.OnClosed ((Toplevel)view);
|
||||
|
||||
if (MdiTop != null && !((Toplevel)view).Modal && view != MdiTop) {
|
||||
MdiTop.OnChildClosed (view as Toplevel);
|
||||
}
|
||||
|
||||
if (toplevels.Count == 1 && Current == MdiTop) {
|
||||
MdiTop.OnAllChildClosed ();
|
||||
if (!MdiTop.IsMdiContainer) {
|
||||
MdiTop = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (toplevels.Count == 0) {
|
||||
Current = null;
|
||||
} else {
|
||||
@@ -648,17 +887,28 @@ namespace Terminal.Gui {
|
||||
MainLoop.MainIteration ();
|
||||
Iteration?.Invoke ();
|
||||
|
||||
EnsureModalAlwaysOnTop (state.Toplevel);
|
||||
if ((state.Toplevel != Current && Current?.Modal == true)
|
||||
|| (state.Toplevel != Current && Current?.Modal == false)) {
|
||||
MdiTop?.OnDeactivate (state.Toplevel);
|
||||
state.Toplevel = Current;
|
||||
MdiTop?.OnActivate (state.Toplevel);
|
||||
Top.SetChildNeedsDisplay ();
|
||||
Refresh ();
|
||||
}
|
||||
if (Driver.EnsureCursorVisibility ()) {
|
||||
state.Toplevel.SetNeedsDisplay ();
|
||||
}
|
||||
} else if (!wait) {
|
||||
return;
|
||||
}
|
||||
if (state.Toplevel != Top && (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) {
|
||||
if (state.Toplevel != Top
|
||||
&& (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) {
|
||||
Top.Redraw (Top.Bounds);
|
||||
state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds);
|
||||
}
|
||||
if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded) {
|
||||
if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded
|
||||
|| MdiChildNeedsDisplay ()) {
|
||||
state.Toplevel.Redraw (state.Toplevel.Bounds);
|
||||
if (DebugDrawBounds) {
|
||||
DrawBounds (state.Toplevel);
|
||||
@@ -668,9 +918,42 @@ namespace Terminal.Gui {
|
||||
} else {
|
||||
Driver.UpdateCursor ();
|
||||
}
|
||||
if (state.Toplevel != Top && !state.Toplevel.Modal
|
||||
&& (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) {
|
||||
Top.Redraw (Top.Bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void EnsureModalAlwaysOnTop (Toplevel toplevel)
|
||||
{
|
||||
if (!toplevel.Running || toplevel == Current || MdiTop == null || toplevels.Peek ().Modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var top in toplevels.Reverse ()) {
|
||||
if (top.Modal && top != Current) {
|
||||
MoveCurrent (top);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool MdiChildNeedsDisplay ()
|
||||
{
|
||||
if (MdiTop == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var top in toplevels) {
|
||||
if (top != Current && top.Visible && (!top.NeedDisplay.IsEmpty || top.ChildNeedsDisplay || top.LayoutNeeded)) {
|
||||
MdiTop.SetChildNeedsDisplay ();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool DebugDrawBounds = false;
|
||||
|
||||
// Need to look into why this does not work properly.
|
||||
@@ -698,14 +981,17 @@ namespace Terminal.Gui {
|
||||
if (_initialized && Driver != null) {
|
||||
var top = new T ();
|
||||
if (top.GetType ().BaseType == typeof (Toplevel)) {
|
||||
Top = top;
|
||||
if (MdiTop == null) {
|
||||
Top = top;
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentException (top.GetType ().BaseType.Name);
|
||||
}
|
||||
Run (top, errorHandler);
|
||||
} else {
|
||||
Init (() => new T ());
|
||||
Run (Top, errorHandler);
|
||||
}
|
||||
Run (Top, errorHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -735,7 +1021,7 @@ namespace Terminal.Gui {
|
||||
/// When <paramref name="errorHandler"/> is null the exception is rethrown, when it returns true the application is resumed and when false method exits gracefully.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="view">The <see cref="Toplevel"/> tu run modally.</param>
|
||||
/// <param name="view">The <see cref="Toplevel"/> to run modally.</param>
|
||||
/// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
|
||||
public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
|
||||
{
|
||||
@@ -763,19 +1049,74 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops running the most recent <see cref="Toplevel"/>.
|
||||
/// Stops running the most recent <see cref="Toplevel"/> or the <paramref name="top"/> if provided.
|
||||
/// </summary>
|
||||
/// <param name="top">The toplevel to request stop.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This will cause <see cref="Application.Run(Func{Exception, bool})"/> to return.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Calling <see cref="Application.RequestStop"/> is equivalent to setting the <see cref="Toplevel.Running"/> property on the curently running <see cref="Toplevel"/> to false.
|
||||
/// Calling <see cref="Application.RequestStop"/> is equivalent to setting the <see cref="Toplevel.Running"/> property on the currently running <see cref="Toplevel"/> to false.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static void RequestStop ()
|
||||
public static void RequestStop (Toplevel top = null)
|
||||
{
|
||||
Current.Running = false;
|
||||
if (MdiTop == null || top == null || (MdiTop == null && top != null)) {
|
||||
top = Current;
|
||||
}
|
||||
|
||||
if (MdiTop != null && top.IsMdiContainer && top?.Running == true
|
||||
&& (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false))) {
|
||||
|
||||
MdiTop.RequestStop ();
|
||||
} else if (MdiTop != null && top != Current && Current?.Running == true && Current?.Modal == true
|
||||
&& top.Modal && top.Running) {
|
||||
|
||||
var ev = new ToplevelClosingEventArgs (Current);
|
||||
Current.OnClosing (ev);
|
||||
if (ev.Cancel) {
|
||||
return;
|
||||
}
|
||||
ev = new ToplevelClosingEventArgs (top);
|
||||
top.OnClosing (ev);
|
||||
if (ev.Cancel) {
|
||||
return;
|
||||
}
|
||||
Current.Running = false;
|
||||
top.Running = false;
|
||||
} else if ((MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false
|
||||
&& Current?.Running == true && !top.Running)
|
||||
|| (MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false
|
||||
&& Current?.Running == false && !top.Running && toplevels.ToArray () [1].Running)) {
|
||||
|
||||
MoveCurrent (top);
|
||||
} else if (MdiTop != null && Current != top && Current?.Running == true && !top.Running
|
||||
&& Current?.Modal == true && top.Modal) {
|
||||
// The Current and the top are both modal so needed to set the Current.Running to false too.
|
||||
Current.Running = false;
|
||||
} else if (MdiTop != null && Current == top && MdiTop?.Running == true && Current?.Running == true && top.Running
|
||||
&& Current?.Modal == true && top.Modal) {
|
||||
// The MdiTop was requested to stop inside a modal toplevel which is the Current and top,
|
||||
// both are the same, so needed to set the Current.Running to false too.
|
||||
Current.Running = false;
|
||||
} else {
|
||||
Toplevel currentTop;
|
||||
if (top == Current || (Current?.Modal == true && !top.Modal)) {
|
||||
currentTop = Current;
|
||||
} else {
|
||||
currentTop = top;
|
||||
}
|
||||
if (!currentTop.Running) {
|
||||
return;
|
||||
}
|
||||
var ev = new ToplevelClosingEventArgs (currentTop);
|
||||
currentTop.OnClosing (ev);
|
||||
if (ev.Cancel) {
|
||||
return;
|
||||
}
|
||||
currentTop.Running = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -804,8 +1145,8 @@ namespace Terminal.Gui {
|
||||
Resized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height });
|
||||
Driver.Clip = full;
|
||||
foreach (var t in toplevels) {
|
||||
t.PositionToplevels ();
|
||||
t.SetRelativeLayout (full);
|
||||
t.PositionToplevels ();
|
||||
t.LayoutSubviews ();
|
||||
}
|
||||
Refresh ();
|
||||
@@ -813,22 +1154,74 @@ namespace Terminal.Gui {
|
||||
|
||||
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;
|
||||
if (MdiTop == null) {
|
||||
foreach (var t in toplevels) {
|
||||
if (t?.SuperView == null && !t.Modal) {
|
||||
t.Frame = full;
|
||||
t.Width = full.Width;
|
||||
t.Height = full.Height;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Top.Frame = full;
|
||||
Top.Width = full.Width;
|
||||
Top.Height = full.Height;
|
||||
}
|
||||
}
|
||||
|
||||
static bool SetCurrentAsTop ()
|
||||
{
|
||||
if (Current != Top && Current?.SuperView == null && !Current.Modal) {
|
||||
if (MdiTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) {
|
||||
Top = Current;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move to the next Mdi child from the <see cref="MdiTop"/>.
|
||||
/// </summary>
|
||||
public static void MoveNext ()
|
||||
{
|
||||
if (MdiTop != null && !Current.Modal) {
|
||||
lock (toplevels) {
|
||||
toplevels.MoveNext ();
|
||||
while (toplevels.Peek () == MdiTop || !toplevels.Peek ().Visible) {
|
||||
toplevels.MoveNext ();
|
||||
}
|
||||
Current = toplevels.Peek ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move to the previous Mdi child from the <see cref="MdiTop"/>.
|
||||
/// </summary>
|
||||
public static void MovePrevious ()
|
||||
{
|
||||
if (MdiTop != null && !Current.Modal) {
|
||||
lock (toplevels) {
|
||||
toplevels.MovePrevious ();
|
||||
while (toplevels.Peek () == MdiTop || !toplevels.Peek ().Visible) {
|
||||
lock (toplevels) {
|
||||
toplevels.MovePrevious ();
|
||||
}
|
||||
}
|
||||
Current = toplevels.Peek ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool ShowChild (Toplevel top)
|
||||
{
|
||||
if (top.Visible && MdiTop != null && Current?.Modal == false) {
|
||||
lock (toplevels) {
|
||||
toplevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
|
||||
Current = top;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
196
Terminal.Gui/Core/StackExtensions.cs
Normal file
196
Terminal.Gui/Core/StackExtensions.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Extension of <see cref="Stack{T}"/> helper to work with specific <see cref="IEqualityComparer{T}"/>
|
||||
/// </summary>
|
||||
public static class StackExtensions {
|
||||
/// <summary>
|
||||
/// Replaces an stack object values that match with the value to replace.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The stack object type.</typeparam>
|
||||
/// <param name="stack">The stack object.</param>
|
||||
/// <param name="valueToReplace">Value to replace.</param>
|
||||
/// <param name="valueToReplaceWith">Value to replace with to what matches the value to replace.</param>
|
||||
/// <param name="comparer">The comparison object.</param>
|
||||
public static void Replace<T> (this Stack<T> stack, T valueToReplace,
|
||||
T valueToReplaceWith, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
|
||||
var temp = new Stack<T> ();
|
||||
while (stack.Count > 0) {
|
||||
var value = stack.Pop ();
|
||||
if (comparer.Equals (value, valueToReplace)) {
|
||||
stack.Push (valueToReplaceWith);
|
||||
break;
|
||||
}
|
||||
temp.Push (value);
|
||||
}
|
||||
|
||||
while (temp.Count > 0)
|
||||
stack.Push (temp.Pop ());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swap two stack objects values that matches with the both values.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The stack object type.</typeparam>
|
||||
/// <param name="stack">The stack object.</param>
|
||||
/// <param name="valueToSwapFrom">Value to swap from.</param>
|
||||
/// <param name="valueToSwapTo">Value to swap to.</param>
|
||||
/// <param name="comparer">The comparison object.</param>
|
||||
public static void Swap<T> (this Stack<T> stack, T valueToSwapFrom,
|
||||
T valueToSwapTo, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
|
||||
int index = stack.Count - 1;
|
||||
T [] stackArr = new T [stack.Count];
|
||||
while (stack.Count > 0) {
|
||||
var value = stack.Pop ();
|
||||
if (comparer.Equals (value, valueToSwapFrom)) {
|
||||
stackArr [index] = valueToSwapTo;
|
||||
} else if (comparer.Equals (value, valueToSwapTo)) {
|
||||
stackArr [index] = valueToSwapFrom;
|
||||
} else {
|
||||
stackArr [index] = value;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
for (int i = 0; i < stackArr.Length; i++)
|
||||
stack.Push (stackArr [i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the first stack object value to the end.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The stack object type.</typeparam>
|
||||
/// <param name="stack">The stack object.</param>
|
||||
public static void MoveNext<T> (this Stack<T> stack)
|
||||
{
|
||||
var temp = new Stack<T> ();
|
||||
var last = stack.Pop ();
|
||||
while (stack.Count > 0) {
|
||||
var value = stack.Pop ();
|
||||
temp.Push (value);
|
||||
}
|
||||
temp.Push (last);
|
||||
|
||||
while (temp.Count > 0)
|
||||
stack.Push (temp.Pop ());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the last stack object value to the top.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The stack object type.</typeparam>
|
||||
/// <param name="stack">The stack object.</param>
|
||||
public static void MovePrevious<T> (this Stack<T> stack)
|
||||
{
|
||||
var temp = new Stack<T> ();
|
||||
T first = default;
|
||||
while (stack.Count > 0) {
|
||||
var value = stack.Pop ();
|
||||
temp.Push (value);
|
||||
if (stack.Count == 1) {
|
||||
first = stack.Pop ();
|
||||
}
|
||||
}
|
||||
|
||||
while (temp.Count > 0)
|
||||
stack.Push (temp.Pop ());
|
||||
stack.Push (first);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all duplicates stack objects values.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The stack object type.</typeparam>
|
||||
/// <param name="stack">The stack object.</param>
|
||||
/// <param name="comparer">The comparison object.</param>
|
||||
/// <returns>The duplicates stack object.</returns>
|
||||
public static Stack<T> FindDuplicates<T> (this Stack<T> stack, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
|
||||
var dup = new Stack<T> ();
|
||||
T [] stackArr = stack.ToArray ();
|
||||
for (int i = 0; i < stackArr.Length; i++) {
|
||||
var value = stackArr [i];
|
||||
for (int j = i + 1; j < stackArr.Length; j++) {
|
||||
var valueToFind = stackArr [j];
|
||||
if (comparer.Equals (value, valueToFind) && !Contains (dup, valueToFind)) {
|
||||
dup.Push (value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the stack object contains the value to find.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The stack object type.</typeparam>
|
||||
/// <param name="stack">The stack object.</param>
|
||||
/// <param name="valueToFind">Value to find.</param>
|
||||
/// <param name="comparer">The comparison object.</param>
|
||||
/// <returns><c>true</c> If the value was found.<c>false</c> otherwise.</returns>
|
||||
public static bool Contains<T> (this Stack<T> stack, T valueToFind, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
|
||||
foreach (T obj in stack) {
|
||||
if (comparer.Equals (obj, valueToFind)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the stack object value to the index.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The stack object type.</typeparam>
|
||||
/// <param name="stack">The stack object.</param>
|
||||
/// <param name="valueToMove">Value to move.</param>
|
||||
/// <param name="index">The index where to move.</param>
|
||||
/// <param name="comparer">The comparison object.</param>
|
||||
public static void MoveTo<T> (this Stack<T> stack, T valueToMove, int index = 0,
|
||||
IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
|
||||
var temp = new Stack<T> ();
|
||||
var toMove = default (T);
|
||||
var stackCount = stack.Count;
|
||||
var count = 0;
|
||||
while (stack.Count > 0) {
|
||||
var value = stack.Pop ();
|
||||
if (comparer.Equals (value, valueToMove)) {
|
||||
toMove = value;
|
||||
break;
|
||||
}
|
||||
temp.Push (value);
|
||||
count++;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
while (stack.Count < stackCount) {
|
||||
if (count - idx == index) {
|
||||
stack.Push (toMove);
|
||||
} else {
|
||||
stack.Push (temp.Pop ());
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,11 @@ namespace Terminal.Gui {
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>.
|
||||
/// They return control to the caller when <see cref="Application.RequestStop()"/> has
|
||||
/// They return control to the caller when <see cref="Application.RequestStop(Toplevel)"/> has
|
||||
/// been called (which sets the <see cref="Toplevel.Running"/> property to false).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A Toplevel is created when an application initialzies Terminal.Gui by callling <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/>.
|
||||
/// A Toplevel is created when an application initializes Terminal.Gui by calling <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/>.
|
||||
/// The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created
|
||||
/// and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and
|
||||
/// call <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>.
|
||||
@@ -67,6 +67,90 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
public event Action Unloaded;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once the Toplevel's <see cref="Application.RunState"/> becomes the <see cref="Application.Current"/>.
|
||||
/// </summary>
|
||||
public event Action<Toplevel> Activate;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once the Toplevel's <see cref="Application.RunState"/> ceases to be the <see cref="Application.Current"/>.
|
||||
/// </summary>
|
||||
public event Action<Toplevel> Deactivate;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once the child Toplevel's <see cref="Application.RunState"/> is closed from the <see cref="Application.End(View)"/>
|
||||
/// </summary>
|
||||
public event Action<Toplevel> ChildClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once the last child Toplevel's <see cref="Application.RunState"/> is closed from the <see cref="Application.End(View)"/>
|
||||
/// </summary>
|
||||
public event Action AllChildClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once the Toplevel's <see cref="Application.RunState"/> is being closing from the <see cref="Application.RequestStop(Toplevel)"/>
|
||||
/// </summary>
|
||||
public event Action<ToplevelClosingEventArgs> Closing;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once the Toplevel's <see cref="Application.RunState"/> is closed from the <see cref="Application.End(View)"/>
|
||||
/// </summary>
|
||||
public event Action<Toplevel> Closed;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once the child Toplevel's <see cref="Application.RunState"/> has begin loaded.
|
||||
/// </summary>
|
||||
public event Action<Toplevel> ChildLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked once the child Toplevel's <see cref="Application.RunState"/> has begin unloaded.
|
||||
/// </summary>
|
||||
public event Action<Toplevel> ChildUnloaded;
|
||||
|
||||
internal virtual void OnChildUnloaded (Toplevel top)
|
||||
{
|
||||
ChildUnloaded?.Invoke (top);
|
||||
}
|
||||
|
||||
internal virtual void OnChildLoaded (Toplevel top)
|
||||
{
|
||||
ChildLoaded?.Invoke (top);
|
||||
}
|
||||
|
||||
internal virtual void OnClosed (Toplevel top)
|
||||
{
|
||||
Closed?.Invoke (top);
|
||||
}
|
||||
|
||||
internal virtual bool OnClosing (ToplevelClosingEventArgs ev)
|
||||
{
|
||||
Closing?.Invoke (ev);
|
||||
return ev.Cancel;
|
||||
}
|
||||
|
||||
internal virtual void OnAllChildClosed ()
|
||||
{
|
||||
AllChildClosed?.Invoke ();
|
||||
}
|
||||
|
||||
internal virtual void OnChildClosed (Toplevel top)
|
||||
{
|
||||
if (IsMdiContainer) {
|
||||
SetChildNeedsDisplay ();
|
||||
}
|
||||
ChildClosed?.Invoke (top);
|
||||
}
|
||||
|
||||
internal virtual void OnDeactivate (Toplevel activated)
|
||||
{
|
||||
Deactivate?.Invoke (activated);
|
||||
}
|
||||
|
||||
internal virtual void OnActivate (Toplevel deactivated)
|
||||
{
|
||||
Activate?.Invoke (deactivated);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> is redraws for the first time.
|
||||
/// </summary>
|
||||
@@ -149,6 +233,20 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
public virtual StatusBar StatusBar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if this Toplevel is a Mdi container.
|
||||
/// </summary>
|
||||
public bool IsMdiContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if this Toplevel is a Mdi child.
|
||||
/// </summary>
|
||||
public bool IsMdiChild {
|
||||
get {
|
||||
return Application.MdiTop != null && Application.MdiTop != this && !Modal;
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override bool OnKeyDown (KeyEvent keyEvent)
|
||||
{
|
||||
@@ -198,7 +296,11 @@ namespace Terminal.Gui {
|
||||
switch (ShortcutHelper.GetModifiersKey (keyEvent)) {
|
||||
case Key.Q | Key.CtrlMask:
|
||||
// FIXED: stop current execution of this container
|
||||
Application.RequestStop ();
|
||||
if (Application.MdiTop != null) {
|
||||
Application.MdiTop.RequestStop ();
|
||||
} else {
|
||||
Application.RequestStop ();
|
||||
}
|
||||
break;
|
||||
case Key.Z | Key.CtrlMask:
|
||||
Driver.Suspend ();
|
||||
@@ -239,16 +341,26 @@ namespace Terminal.Gui {
|
||||
return true;
|
||||
case Key.Tab | Key.CtrlMask:
|
||||
case Key key when key == Application.AlternateForwardKey: // Needed on Unix
|
||||
Application.Top.FocusNext ();
|
||||
if (Application.Top.Focused == null) {
|
||||
if (Application.MdiTop == null) {
|
||||
Application.Top.FocusNext ();
|
||||
if (Application.Top.Focused == null) {
|
||||
Application.Top.FocusNext ();
|
||||
}
|
||||
Application.Top.SetNeedsDisplay ();
|
||||
} else {
|
||||
MoveNext ();
|
||||
}
|
||||
return true;
|
||||
case Key.Tab | Key.ShiftMask | Key.CtrlMask:
|
||||
case Key key when key == Application.AlternateBackwardKey: // Needed on Unix
|
||||
Application.Top.FocusPrev ();
|
||||
if (Application.Top.Focused == null) {
|
||||
if (Application.MdiTop == null) {
|
||||
Application.Top.FocusPrev ();
|
||||
if (Application.Top.Focused == null) {
|
||||
Application.Top.FocusPrev ();
|
||||
}
|
||||
Application.Top.SetNeedsDisplay ();
|
||||
} else {
|
||||
MovePrevious ();
|
||||
}
|
||||
return true;
|
||||
case Key.L | Key.CtrlMask:
|
||||
@@ -383,7 +495,7 @@ namespace Terminal.Gui {
|
||||
//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
|
||||
bool m, s;
|
||||
if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
|
||||
m = Application.Top.MenuBar != null;
|
||||
m = Application.MdiTop?.MenuBar != null || Application.Top.MenuBar != null;
|
||||
} else {
|
||||
m = ((Toplevel)SuperView).MenuBar != null;
|
||||
}
|
||||
@@ -394,7 +506,9 @@ namespace Terminal.Gui {
|
||||
}
|
||||
ny = Math.Max (y, l);
|
||||
if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
|
||||
s = Application.Top.StatusBar != null && Application.Top.StatusBar.Visible;
|
||||
s = (Application.MdiTop != null && Application.MdiTop.StatusBar != null
|
||||
&& Application.MdiTop.StatusBar.Visible)
|
||||
|| (Application.Top.StatusBar != null && Application.Top.StatusBar.Visible);
|
||||
} else {
|
||||
s = ((Toplevel)SuperView).StatusBar != null && ((Toplevel)SuperView).StatusBar.Visible;
|
||||
}
|
||||
@@ -429,7 +543,8 @@ namespace Terminal.Gui {
|
||||
public virtual void PositionToplevel (Toplevel top)
|
||||
{
|
||||
EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
|
||||
if (top?.SuperView != null && (nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
|
||||
if ((top?.SuperView != null || Application.MdiTop != null && top != Application.MdiTop)
|
||||
&& (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;
|
||||
}
|
||||
@@ -437,16 +552,28 @@ namespace Terminal.Gui {
|
||||
top.Y = ny;
|
||||
}
|
||||
}
|
||||
var statusBar = top?.SuperView != null && top.SuperView is Toplevel toplevel
|
||||
? toplevel.StatusBar : null;
|
||||
|
||||
View superView = null;
|
||||
StatusBar statusBar = null;
|
||||
|
||||
if (top != Application.MdiTop && Application.MdiTop != null && Application.MdiTop.StatusBar != null) {
|
||||
superView = Application.MdiTop;
|
||||
statusBar = Application.MdiTop.StatusBar;
|
||||
} else if (top?.SuperView != null && top.SuperView is Toplevel toplevel) {
|
||||
superView = top.SuperView;
|
||||
statusBar = toplevel.StatusBar;
|
||||
}
|
||||
if (statusBar != null) {
|
||||
if (ny + top.Frame.Height != top.SuperView.Frame.Height - (statusBar.Visible ? 1 : 0)) {
|
||||
if (ny + top.Frame.Height >= 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 (superView == Application.MdiTop) {
|
||||
top.SetRelativeLayout (superView.Frame);
|
||||
} else {
|
||||
superView.LayoutSubviews ();
|
||||
}
|
||||
}
|
||||
if (top.StatusBar != null) {
|
||||
if (top.StatusBar.Frame.Y != top.Frame.Height - (top.StatusBar.Visible ? 1 : 0)) {
|
||||
@@ -460,30 +587,151 @@ namespace Terminal.Gui {
|
||||
///<inheritdoc/>
|
||||
public override void Redraw (Rect bounds)
|
||||
{
|
||||
if (IsCurrentTop || this == Application.Top || Application.Current.GetType ().BaseType == typeof (Toplevel)) {
|
||||
if (!NeedDisplay.IsEmpty || LayoutNeeded) {
|
||||
Driver.SetAttribute (Colors.TopLevel.Normal);
|
||||
if (this == Application.MdiTop) {
|
||||
RedrawMdi (bounds);
|
||||
} else {
|
||||
if (!CanBeVisible (this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) {
|
||||
Driver.SetAttribute (ColorScheme.Normal);
|
||||
|
||||
// This is the Application.Top. Clear just the region we're being asked to redraw
|
||||
// (the bounds passed to us).
|
||||
// Must be the screen-relative region to clear, not the bounds.
|
||||
Clear (Frame);
|
||||
Driver.SetAttribute (Colors.Base.Normal);
|
||||
PositionToplevels ();
|
||||
|
||||
foreach (var view in Subviews) {
|
||||
if (view.Frame.IntersectsWith (bounds)) {
|
||||
view.SetNeedsLayout ();
|
||||
view.SetNeedsDisplay (view.Bounds);
|
||||
}
|
||||
}
|
||||
|
||||
ClearLayoutNeeded ();
|
||||
ClearNeedsDisplay ();
|
||||
}
|
||||
}
|
||||
|
||||
base.Redraw (base.Bounds);
|
||||
if (LayoutStyle == LayoutStyle.Computed)
|
||||
SetRelativeLayout (Bounds);
|
||||
PositionToplevels ();
|
||||
LayoutSubviews ();
|
||||
|
||||
foreach (var view in Subviews) {
|
||||
if (view.Frame.IntersectsWith (bounds) && !OutsideTopFrame (this)) {
|
||||
view.SetNeedsLayout ();
|
||||
view.SetNeedsDisplay (view.Bounds);
|
||||
view.Redraw (view.Bounds);
|
||||
}
|
||||
}
|
||||
|
||||
ClearLayoutNeeded ();
|
||||
ClearNeedsDisplay ();
|
||||
}
|
||||
|
||||
void RedrawMdi (Rect bounds)
|
||||
{
|
||||
if (!IsMdiContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) {
|
||||
Driver.SetAttribute (ColorScheme.Normal);
|
||||
|
||||
Clear (Frame);
|
||||
|
||||
Driver.SetAttribute (Colors.Base.Normal);
|
||||
|
||||
if (LayoutStyle == LayoutStyle.Computed)
|
||||
SetRelativeLayout (Bounds);
|
||||
PositionToplevels ();
|
||||
LayoutSubviews ();
|
||||
|
||||
foreach (var top in Application.MdiChildes.AsEnumerable ().Reverse ()) {
|
||||
if (top.Frame.IntersectsWith (bounds)) {
|
||||
if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) {
|
||||
top.SetNeedsLayout ();
|
||||
top.SetNeedsDisplay (top.Bounds);
|
||||
top.Redraw (top.Bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClearLayoutNeeded ();
|
||||
ClearNeedsDisplay ();
|
||||
}
|
||||
}
|
||||
|
||||
bool OutsideTopFrame (Toplevel top)
|
||||
{
|
||||
if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// FIXED:It does not look like the event is raised on clicked-drag
|
||||
// need to figure that out.
|
||||
//
|
||||
internal static Point? dragPosition;
|
||||
Point start;
|
||||
|
||||
///<inheritdoc/>
|
||||
public override bool MouseEvent (MouseEvent mouseEvent)
|
||||
{
|
||||
// FIXED:The code is currently disabled, because the
|
||||
// Driver.UncookMouse does not seem to have an effect if there is
|
||||
// a pending mouse event activated.
|
||||
|
||||
int nx, ny;
|
||||
if (!dragPosition.HasValue && mouseEvent.Flags == (MouseFlags.Button1Pressed)) {
|
||||
// Only start grabbing if the user clicks on the title bar.
|
||||
if (mouseEvent.Y == 0) {
|
||||
start = new Point (mouseEvent.X, mouseEvent.Y);
|
||||
dragPosition = new Point ();
|
||||
nx = mouseEvent.X - mouseEvent.OfX;
|
||||
ny = mouseEvent.Y - mouseEvent.OfY;
|
||||
dragPosition = new Point (nx, ny);
|
||||
Application.GrabMouse (this);
|
||||
}
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
|
||||
return true;
|
||||
} else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
|
||||
mouseEvent.Flags == MouseFlags.Button3Pressed) {
|
||||
if (dragPosition.HasValue) {
|
||||
if (SuperView == null) {
|
||||
// Redraw the entire app window using just our Frame. Since we are
|
||||
// Application.Top, and our Frame always == our Bounds (Location is always (0,0))
|
||||
// our Frame is actually view-relative (which is what Redraw takes).
|
||||
// We need to pass all the view bounds because since the windows was
|
||||
// moved around, we don't know exactly what was the affected region.
|
||||
Application.Top.SetNeedsDisplay ();
|
||||
} else {
|
||||
SuperView.SetNeedsDisplay ();
|
||||
}
|
||||
EnsureVisibleBounds (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - start.X : Frame.X - start.X),
|
||||
mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny);
|
||||
|
||||
dragPosition = new Point (nx, ny);
|
||||
LayoutSubviews ();
|
||||
Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
|
||||
if (X == null || X is Pos.PosAbsolute) {
|
||||
X = nx;
|
||||
}
|
||||
if (Y == null || Y is Pos.PosAbsolute) {
|
||||
Y = ny;
|
||||
}
|
||||
//System.Diagnostics.Debug.WriteLine ($"nx:{nx},ny:{ny}");
|
||||
|
||||
// FIXED: optimize, only SetNeedsDisplay on the before/after regions.
|
||||
SetNeedsDisplay ();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseEvent.Flags == MouseFlags.Button1Released && dragPosition.HasValue) {
|
||||
Application.UngrabMouse ();
|
||||
Driver.UncookMouse ();
|
||||
dragPosition = null;
|
||||
}
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine (mouseEvent.ToString ());
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -494,5 +742,209 @@ namespace Terminal.Gui {
|
||||
{
|
||||
FocusFirst ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move to the next Mdi child from the <see cref="Application.MdiTop"/>.
|
||||
/// </summary>
|
||||
public virtual void MoveNext ()
|
||||
{
|
||||
Application.MoveNext ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move to the previous Mdi child from the <see cref="Application.MdiTop"/>.
|
||||
/// </summary>
|
||||
public virtual void MovePrevious ()
|
||||
{
|
||||
Application.MovePrevious ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops running this <see cref="Toplevel"/>.
|
||||
/// </summary>
|
||||
public virtual void RequestStop ()
|
||||
{
|
||||
if (IsMdiContainer && Running
|
||||
&& (Application.Current == this
|
||||
|| Application.Current?.Modal == false
|
||||
|| Application.Current?.Modal == true && Application.Current?.Running == false)) {
|
||||
|
||||
foreach (var child in Application.MdiChildes) {
|
||||
var ev = new ToplevelClosingEventArgs (this);
|
||||
if (child.OnClosing (ev)) {
|
||||
return;
|
||||
}
|
||||
child.Running = false;
|
||||
Application.RequestStop (child);
|
||||
}
|
||||
Running = false;
|
||||
Application.RequestStop (this);
|
||||
} else if (IsMdiContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) {
|
||||
var ev = new ToplevelClosingEventArgs (Application.Current);
|
||||
if (OnClosing (ev)) {
|
||||
return;
|
||||
}
|
||||
Application.RequestStop (Application.Current);
|
||||
} else if (!IsMdiContainer && Running && (!Modal || (Modal && Application.Current != this))) {
|
||||
var ev = new ToplevelClosingEventArgs (this);
|
||||
if (OnClosing (ev)) {
|
||||
return;
|
||||
}
|
||||
Running = false;
|
||||
Application.RequestStop (this);
|
||||
} else {
|
||||
Application.RequestStop (Application.Current);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops running the <paramref name="top"/> <see cref="Toplevel"/>.
|
||||
/// </summary>
|
||||
/// <param name="top">The toplevel to request stop.</param>
|
||||
public virtual void RequestStop (Toplevel top)
|
||||
{
|
||||
top.RequestStop ();
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public override void PositionCursor ()
|
||||
{
|
||||
if (!IsMdiContainer) {
|
||||
base.PositionCursor ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Focused == null) {
|
||||
foreach (var top in Application.MdiChildes) {
|
||||
if (top != this && top.Visible) {
|
||||
top.SetFocus ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
base.PositionCursor ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current visible toplevel Mdi child that match the arguments pattern.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="exclude">The strings to exclude.</param>
|
||||
/// <returns>The matched view.</returns>
|
||||
public View GetTopMdiChild (Type type = null, string [] exclude = null)
|
||||
{
|
||||
if (Application.MdiTop == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var top in Application.MdiChildes) {
|
||||
if (type != null && top.GetType () == type
|
||||
&& exclude?.Contains (top.Data.ToString ()) == false) {
|
||||
return top;
|
||||
} else if ((type != null && top.GetType () != type)
|
||||
|| (exclude?.Contains (top.Data.ToString ()) == true)) {
|
||||
continue;
|
||||
}
|
||||
return top;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the Mdi child indicated by the <paramref name="top"/> setting as <see cref="Application.Current"/>.
|
||||
/// </summary>
|
||||
/// <param name="top">The toplevel.</param>
|
||||
/// <returns><see langword="true"/> if the toplevel can be showed.<see langword="false"/> otherwise.</returns>
|
||||
public virtual bool ShowChild (Toplevel top = null)
|
||||
{
|
||||
if (Application.MdiTop != null) {
|
||||
return Application.ShowChild (top == null ? this : top);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the <see cref="IEqualityComparer{T}"/> to comparing two <see cref="Toplevel"/> used by <see cref="StackExtensions"/>.
|
||||
/// </summary>
|
||||
public class ToplevelEqualityComparer : IEqualityComparer<Toplevel> {
|
||||
/// <summary>Determines whether the specified objects are equal.</summary>
|
||||
/// <param name="x">The first object of type <see cref="Toplevel" /> to compare.</param>
|
||||
/// <param name="y">The second object of type <see cref="Toplevel" /> to compare.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the specified objects are equal; otherwise, <see langword="false" />.</returns>
|
||||
public bool Equals (Toplevel x, Toplevel y)
|
||||
{
|
||||
if (y == null && x == null)
|
||||
return true;
|
||||
else if (x == null || y == null)
|
||||
return false;
|
||||
else if (x.Id == y.Id)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Returns a hash code for the specified object.</summary>
|
||||
/// <param name="obj">The <see cref="Toplevel" /> for which a hash code is to be returned.</param>
|
||||
/// <returns>A hash code for the specified object.</returns>
|
||||
/// <exception cref="ArgumentNullException">The type of <paramref name="obj" /> is a reference type and <paramref name="obj" /> is <see langword="null" />.</exception>
|
||||
public int GetHashCode (Toplevel obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException ();
|
||||
|
||||
int hCode = 0;
|
||||
if (int.TryParse (obj.Id.ToString (), out int result)) {
|
||||
hCode = result;
|
||||
}
|
||||
return hCode.GetHashCode ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the <see cref="IComparer{T}"/> to sort the <see cref="Toplevel"/> from the <see cref="Application.MdiChildes"/> if needed.
|
||||
/// </summary>
|
||||
public sealed class ToplevelComparer : IComparer<Toplevel> {
|
||||
/// <summary>Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.</summary>
|
||||
/// <param name="x">The first object to compare.</param>
|
||||
/// <param name="y">The second object to compare.</param>
|
||||
/// <returns>A signed integer that indicates the relative values of <paramref name="x" /> and <paramref name="y" />, as shown in the following table.Value Meaning Less than zero
|
||||
/// <paramref name="x" /> is less than <paramref name="y" />.Zero
|
||||
/// <paramref name="x" /> equals <paramref name="y" />.Greater than zero
|
||||
/// <paramref name="x" /> is greater than <paramref name="y" />.</returns>
|
||||
public int Compare (Toplevel x, Toplevel y)
|
||||
{
|
||||
if (ReferenceEquals (x, y))
|
||||
return 0;
|
||||
else if (x == null)
|
||||
return -1;
|
||||
else if (y == null)
|
||||
return 1;
|
||||
else
|
||||
return string.Compare (x.Id.ToString (), y.Id.ToString ());
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// <see cref="EventArgs"/> implementation for the <see cref="Toplevel.Closing"/> event.
|
||||
/// </summary>
|
||||
public class ToplevelClosingEventArgs : EventArgs {
|
||||
/// <summary>
|
||||
/// The toplevel requesting stop.
|
||||
/// </summary>
|
||||
public View RequestingTop { get; }
|
||||
/// <summary>
|
||||
/// Provides an event cancellation option.
|
||||
/// </summary>
|
||||
public bool Cancel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the event arguments with the requesting toplevel.
|
||||
/// </summary>
|
||||
/// <param name="requestingTop">The <see cref="RequestingTop"/>.</param>
|
||||
public ToplevelClosingEventArgs (Toplevel requestingTop)
|
||||
{
|
||||
RequestingTop = requestingTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
}
|
||||
|
||||
private int GetTabIndex (int idx)
|
||||
int GetTabIndex (int idx)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var v in SuperView.tabIndexes) {
|
||||
@@ -280,7 +280,7 @@ namespace Terminal.Gui {
|
||||
return Math.Min (i, idx);
|
||||
}
|
||||
|
||||
private void SetTabIndex ()
|
||||
void SetTabIndex ()
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var v in SuperView.tabIndexes) {
|
||||
@@ -989,8 +989,8 @@ namespace Terminal.Gui {
|
||||
internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
|
||||
{
|
||||
// Computes the real row, col relative to the screen.
|
||||
rrow = row + frame.Y;
|
||||
rcol = col + frame.X;
|
||||
rrow = Math.Max (row + frame.Y, 0);
|
||||
rcol = Math.Max (col + frame.X, 0);
|
||||
var ccontainer = container;
|
||||
while (ccontainer != null) {
|
||||
rrow += ccontainer.frame.Y;
|
||||
@@ -1338,18 +1338,7 @@ namespace Terminal.Gui {
|
||||
|
||||
var clipRect = new Rect (Point.Empty, frame.Size);
|
||||
|
||||
if (ColorScheme != null) {
|
||||
Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
|
||||
}
|
||||
|
||||
if (!ustring.IsNullOrEmpty (Text)) {
|
||||
Clear ();
|
||||
// Draw any Text
|
||||
if (textFormatter != null) {
|
||||
textFormatter.NeedsFormat = true;
|
||||
}
|
||||
textFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : ColorScheme.Normal, HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
|
||||
}
|
||||
DrawText ();
|
||||
|
||||
// Invoke DrawContentEvent
|
||||
OnDrawContent (bounds);
|
||||
@@ -1377,6 +1366,33 @@ namespace Terminal.Gui {
|
||||
ClearNeedsDisplay ();
|
||||
}
|
||||
|
||||
void DrawText ()
|
||||
{
|
||||
if (ColorScheme != null) {
|
||||
Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
|
||||
}
|
||||
|
||||
if (!ustring.IsNullOrEmpty (Text)) {
|
||||
var savedClip = ClipToBounds ();
|
||||
Rect viewBounds = Bounds;
|
||||
if (SuperView != null && viewBounds.Width > SuperView.Bounds.Width) {
|
||||
viewBounds.Width = SuperView.Bounds.Width;
|
||||
}
|
||||
if (SuperView != null && viewBounds.Height > SuperView.Bounds.Height) {
|
||||
viewBounds.Height = SuperView.Bounds.Height;
|
||||
}
|
||||
var viewFrame = ViewToScreen (viewBounds);
|
||||
Clear (viewFrame);
|
||||
// Draw any Text
|
||||
if (textFormatter != null) {
|
||||
textFormatter.NeedsFormat = true;
|
||||
}
|
||||
textFormatter?.Draw (viewFrame, HasFocus ? ColorScheme.Focus : ColorScheme.Normal, HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
|
||||
|
||||
Driver.Clip = savedClip;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when the content area of the View is to be drawn.
|
||||
/// </summary>
|
||||
@@ -1957,8 +1973,9 @@ namespace Terminal.Gui {
|
||||
v.LayoutNeeded = false;
|
||||
}
|
||||
|
||||
if (SuperView == Application.Top && LayoutNeeded && ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
|
||||
SetRelativeLayout (Frame);
|
||||
if (SuperView != null && SuperView == Application.Top && LayoutNeeded
|
||||
&& ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
|
||||
SetRelativeLayout (SuperView.Frame);
|
||||
}
|
||||
|
||||
LayoutNeeded = false;
|
||||
@@ -2259,7 +2276,7 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
public bool Visible { get; set; } = true;
|
||||
|
||||
bool CanBeVisible (View view)
|
||||
internal bool CanBeVisible (View view)
|
||||
{
|
||||
if (!view.Visible) {
|
||||
return false;
|
||||
|
||||
@@ -209,77 +209,6 @@ namespace Terminal.Gui {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// FIXED:It does not look like the event is raised on clicked-drag
|
||||
// need to figure that out.
|
||||
//
|
||||
internal static Point? dragPosition;
|
||||
Point start;
|
||||
|
||||
///<inheritdoc/>
|
||||
public override bool MouseEvent (MouseEvent mouseEvent)
|
||||
{
|
||||
// FIXED:The code is currently disabled, because the
|
||||
// Driver.UncookMouse does not seem to have an effect if there is
|
||||
// a pending mouse event activated.
|
||||
|
||||
int nx, ny;
|
||||
if (!dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed
|
||||
|| mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
|
||||
// Only start grabbing if the user clicks on the title bar.
|
||||
if (mouseEvent.Y == 0) {
|
||||
start = new Point (mouseEvent.X, mouseEvent.Y);
|
||||
dragPosition = new Point ();
|
||||
nx = mouseEvent.X - mouseEvent.OfX;
|
||||
ny = mouseEvent.Y - mouseEvent.OfY;
|
||||
dragPosition = new Point (nx, ny);
|
||||
Application.GrabMouse (this);
|
||||
}
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
|
||||
return true;
|
||||
} else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
|
||||
mouseEvent.Flags == MouseFlags.Button3Pressed) {
|
||||
if (dragPosition.HasValue) {
|
||||
if (SuperView == null) {
|
||||
Application.Top.SetNeedsDisplay (Frame);
|
||||
// Redraw the entire app window using just our Frame. Since we are
|
||||
// Application.Top, and our Frame always == our Bounds (Location is always (0,0))
|
||||
// our Frame is actually view-relative (which is what Redraw takes).
|
||||
Application.Top.Redraw (Frame);
|
||||
} else {
|
||||
SuperView.SetNeedsDisplay (Frame);
|
||||
}
|
||||
EnsureVisibleBounds (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - start.X : Frame.X - start.X),
|
||||
mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny);
|
||||
|
||||
dragPosition = new Point (nx, ny);
|
||||
LayoutSubviews ();
|
||||
Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
|
||||
if (X == null || X is Pos.PosAbsolute) {
|
||||
X = nx;
|
||||
}
|
||||
if (Y == null || Y is Pos.PosAbsolute) {
|
||||
Y = ny;
|
||||
}
|
||||
//System.Diagnostics.Debug.WriteLine ($"nx:{nx},ny:{ny}");
|
||||
|
||||
// FIXED: optimize, only SetNeedsDisplay on the before/after regions.
|
||||
SetNeedsDisplay ();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseEvent.Flags == MouseFlags.Button1Released && dragPosition.HasValue) {
|
||||
Application.UngrabMouse ();
|
||||
Driver.UncookMouse ();
|
||||
dragPosition = null;
|
||||
}
|
||||
|
||||
//System.Diagnostics.Debug.WriteLine (mouseEvent.ToString ());
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text displayed by the <see cref="Label"/>.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user