diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs
index 95d3dadada..a880e6f2c8 100644
--- a/Terminal.Gui/Core/Application.cs
+++ b/Terminal.Gui/Core/Application.cs
@@ -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 @@
namespace Terminal.Gui {
///
- /// 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.
///
///
///
@@ -55,11 +55,43 @@ namespace Terminal.Gui {
///
///
public static class Application {
+ static Stack toplevels = new Stack ();
+
///
/// The current in use.
///
public static ConsoleDriver Driver;
-
+
+ ///
+ /// Gets all the Mdi childes which represent all the not modal from the .
+ ///
+ public static List MdiChildes {
+ get {
+ if (MdiTop != null) {
+ List mdiChildes = new List ();
+ foreach (var top in toplevels) {
+ if (top != MdiTop && !top.Modal) {
+ mdiChildes.Add (top);
+ }
+ }
+ return mdiChildes;
+ }
+ return null;
+ }
+ }
+
+ ///
+ /// The object used for the application on startup which is true.
+ ///
+ public static Toplevel MdiTop {
+ get {
+ if (Top.IsMdiContainer) {
+ return Top;
+ }
+ return null;
+ }
+ }
+
///
/// The object used for the application on startup ()
///
@@ -125,8 +157,6 @@ public static bool AlwaysSetPosition {
/// The main loop.
public static MainLoop MainLoop { get; private set; }
- static Stack toplevels = new Stack ();
-
///
/// This event is raised on each iteration of the
///
@@ -349,6 +379,72 @@ static void ProcessKeyUpEvent (KeyEvent ke)
}
}
+ 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 +476,18 @@ static View FindDeepestView (View start, int x, int y, out int resx, out int res
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;
///
@@ -441,6 +549,17 @@ static void ProcessMouseEvent (MouseEvent me)
}
}
+ 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 +592,56 @@ static void ProcessMouseEvent (MouseEvent me)
}
}
+ // 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 +662,12 @@ static bool OutsideFrame (Point p, Rect r)
///
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,17 +679,67 @@ public static RunState Begin (Toplevel toplevel)
initializable.BeginInit ();
initializable.EndInit ();
}
- toplevels.Push (toplevel);
- Current = toplevel;
+
+ 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) {
+ Top = toplevel;
+ }
+
+ 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;
}
@@ -530,7 +753,11 @@ public static void End (RunState runState)
if (runState == null)
throw new ArgumentNullException (nameof (runState));
- runState.Toplevel.OnUnloaded ();
+ if (MdiTop != null) {
+ MdiTop.OnChildUnloaded (runState.Toplevel);
+ } else {
+ runState.Toplevel.OnUnloaded ();
+ }
runState.Dispose ();
}
@@ -544,9 +771,10 @@ public static void Shutdown ()
// 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
@@ -595,8 +823,10 @@ public static void Refresh ()
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 ();
@@ -609,10 +839,21 @@ internal static void End (View view)
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 == 0) {
Current = null;
} else {
Current = toplevels.Peek ();
+ if (toplevels.Count == 1 && Current == MdiTop) {
+ MdiTop.OnAllChildClosed ();
+ } else {
+ SetCurrentAsTop ();
+ }
Refresh ();
}
}
@@ -644,18 +885,29 @@ public static void RunLoop (RunState state, bool wait = true)
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);
@@ -665,7 +917,40 @@ public static void RunLoop (RunState state, bool wait = true)
} 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;
@@ -692,8 +977,16 @@ public static void Run (Func errorHandler = null)
///
public static void Run (Func errorHandler = null) where T : Toplevel, new()
{
- Init (() => new T ());
- Run (Top, errorHandler);
+ if (_initialized && Driver != null) {
+ var top = new T ();
+ if (top.GetType ().BaseType != typeof (Toplevel)) {
+ throw new ArgumentException (top.GetType ().BaseType.Name);
+ }
+ Run (top, errorHandler);
+ } else {
+ Init (() => new T ());
+ Run (Top, errorHandler);
+ }
}
///
@@ -723,7 +1016,7 @@ public static void Run (Func errorHandler = null)
/// When is null the exception is rethrown, when it returns true the application is resumed and when false method exits gracefully.
///
///
- /// The tu run modally.
+ /// The to run modally.
/// Handler for any unhandled exceptions (resumes when returns true, rethrows when null).
public static void Run (Toplevel view, Func errorHandler = null)
{
@@ -751,19 +1044,74 @@ public static void Run (Toplevel view, Func errorHandler = null
}
///
- /// Stops running the most recent .
+ /// Stops running the most recent or the if provided.
///
+ /// The toplevel to request stop.
///
///
/// This will cause to return.
///
///
- /// Calling is equivalent to setting the property on the curently running to false.
+ /// Calling is equivalent to setting the property on the currently running to false.
///
///
- 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;
+ }
}
///
@@ -788,17 +1136,95 @@ public class ResizedEventArgs : EventArgs {
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) {
- t.PositionToplevels ();
t.SetRelativeLayout (full);
+ t.PositionToplevels ();
t.LayoutSubviews ();
}
Refresh ();
}
+
+ static void SetToplevelsSize (Rect full)
+ {
+ 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 (MdiTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) {
+ Top = Current;
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Move to the next Mdi child from the .
+ ///
+ 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 ();
+ }
+ }
+ }
+
+ ///
+ /// Move to the previous Mdi child from the .
+ ///
+ 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;
+ }
+
+ ///
+ /// Wakes up the mainloop that might be waiting on input, must be thread safe.
+ ///
+ public static void DoEvents ()
+ {
+ MainLoop.Driver.Wakeup ();
+ }
}
}
diff --git a/Terminal.Gui/Core/StackExtensions.cs b/Terminal.Gui/Core/StackExtensions.cs
new file mode 100644
index 0000000000..1d433e33f3
--- /dev/null
+++ b/Terminal.Gui/Core/StackExtensions.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Collections.Generic;
+
+namespace Terminal.Gui {
+ ///
+ /// Extension of helper to work with specific
+ ///
+ public static class StackExtensions {
+ ///
+ /// Replaces an stack object values that match with the value to replace.
+ ///
+ /// The stack object type.
+ /// The stack object.
+ /// Value to replace.
+ /// Value to replace with to what matches the value to replace.
+ /// The comparison object.
+ public static void Replace (this Stack stack, T valueToReplace,
+ T valueToReplaceWith, IEqualityComparer comparer = null)
+ {
+ comparer = comparer ?? EqualityComparer.Default;
+
+ var temp = new Stack ();
+ 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 ());
+ }
+
+ ///
+ /// Swap two stack objects values that matches with the both values.
+ ///
+ /// The stack object type.
+ /// The stack object.
+ /// Value to swap from.
+ /// Value to swap to.
+ /// The comparison object.
+ public static void Swap (this Stack stack, T valueToSwapFrom,
+ T valueToSwapTo, IEqualityComparer comparer = null)
+ {
+ comparer = comparer ?? EqualityComparer.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]);
+ }
+
+ ///
+ /// Move the first stack object value to the end.
+ ///
+ /// The stack object type.
+ /// The stack object.
+ public static void MoveNext (this Stack stack)
+ {
+ var temp = new Stack ();
+ 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 ());
+ }
+
+ ///
+ /// Move the last stack object value to the top.
+ ///
+ /// The stack object type.
+ /// The stack object.
+ public static void MovePrevious (this Stack stack)
+ {
+ var temp = new Stack ();
+ 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);
+ }
+
+ ///
+ /// Find all duplicates stack objects values.
+ ///
+ /// The stack object type.
+ /// The stack object.
+ /// The comparison object.
+ /// The duplicates stack object.
+ public static Stack FindDuplicates (this Stack stack, IEqualityComparer comparer = null)
+ {
+ comparer = comparer ?? EqualityComparer.Default;
+
+ var dup = new Stack ();
+ 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;
+ }
+
+ ///
+ /// Check if the stack object contains the value to find.
+ ///
+ /// The stack object type.
+ /// The stack object.
+ /// Value to find.
+ /// The comparison object.
+ /// true If the value was found.false otherwise.
+ public static bool Contains (this Stack stack, T valueToFind, IEqualityComparer comparer = null)
+ {
+ comparer = comparer ?? EqualityComparer.Default;
+
+ foreach (T obj in stack) {
+ if (comparer.Equals (obj, valueToFind)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Move the stack object value to the index.
+ ///
+ /// The stack object type.
+ /// The stack object.
+ /// Value to move.
+ /// The index where to move.
+ /// The comparison object.
+ public static void MoveTo (this Stack stack, T valueToMove, int index = 0,
+ IEqualityComparer comparer = null)
+ {
+ if (index < 0) {
+ return;
+ }
+
+ comparer = comparer ?? EqualityComparer.Default;
+
+ var temp = new Stack ();
+ 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++;
+ }
+ }
+ }
+}
diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs
index a933f9a059..95453b709b 100644
--- a/Terminal.Gui/Core/Toplevel.cs
+++ b/Terminal.Gui/Core/Toplevel.cs
@@ -16,11 +16,11 @@ namespace Terminal.Gui {
///
///
/// Toplevels can be modally executing views, started by calling .
- /// They return control to the caller when has
+ /// They return control to the caller when has
/// been called (which sets the property to false).
///
///
- /// A Toplevel is created when an application initialzies Terminal.Gui by callling .
+ /// A Toplevel is created when an application initializes Terminal.Gui by calling .
/// The application Toplevel can be accessed via . Additional Toplevels can be created
/// and run (e.g. s. To run a Toplevel, create the and
/// call .
@@ -67,6 +67,90 @@ public class Toplevel : View {
///
public event Action Unloaded;
+ ///
+ /// Invoked once the Toplevel's becomes the .
+ ///
+ public event Action Activate;
+
+ ///
+ /// Invoked once the Toplevel's ceases to be the .
+ ///
+ public event Action Deactivate;
+
+ ///
+ /// Invoked once the child Toplevel's is closed from the
+ ///
+ public event Action ChildClosed;
+
+ ///
+ /// Invoked once the last child Toplevel's is closed from the
+ ///
+ public event Action AllChildClosed;
+
+ ///
+ /// Invoked once the Toplevel's is being closing from the
+ ///
+ public event Action Closing;
+
+ ///
+ /// Invoked once the Toplevel's is closed from the
+ ///
+ public event Action Closed;
+
+ ///
+ /// Invoked once the child Toplevel's has begin loaded.
+ ///
+ public event Action ChildLoaded;
+
+ ///
+ /// Invoked once the child Toplevel's has begin unloaded.
+ ///
+ public event Action 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);
+ }
+
///
/// Called from before the is redraws for the first time.
///
@@ -112,7 +196,7 @@ public Toplevel () : base ()
void Initialize ()
{
- ColorScheme = Colors.Base;
+ ColorScheme = Colors.TopLevel;
}
///
@@ -142,12 +226,26 @@ public override bool CanFocus {
///
/// 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; }
+
+ ///
+ /// Gets or sets if this Toplevel is a Mdi container.
+ ///
+ public bool IsMdiContainer { get; set; }
+
+ ///
+ /// Gets or sets if this Toplevel is a Mdi child.
+ ///
+ public bool IsMdiChild {
+ get {
+ return Application.MdiTop != null && Application.MdiTop != this && !Modal;
+ }
+ }
///
public override bool OnKeyDown (KeyEvent keyEvent)
@@ -198,7 +296,11 @@ public override bool ProcessKey (KeyEvent keyEvent)
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 ();
@@ -234,21 +336,31 @@ public override bool ProcessKey (KeyEvent keyEvent)
old?.SetNeedsDisplay ();
Focused?.SetNeedsDisplay ();
} else {
- FocusNearestView (SuperView?.TabIndexes?.Reverse(), Direction.Backward);
+ FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward);
}
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:
@@ -265,7 +377,7 @@ public override bool ProcessColdKey (KeyEvent keyEvent)
return true;
}
- if (ShortcutHelper.FindAndOpenByShortcut(keyEvent, this)) {
+ if (ShortcutHelper.FindAndOpenByShortcut (keyEvent, this)) {
return true;
}
return false;
@@ -319,9 +431,7 @@ void FocusNearestView (IEnumerable views, Direction direction)
///
public override void Add (View view)
{
- if (this == Application.Top) {
- AddMenuStatusBar (view);
- }
+ AddMenuStatusBar (view);
base.Add (view);
}
@@ -424,10 +534,15 @@ internal void PositionToplevels ()
}
}
- 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 || top != Application.Top)
+ && (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 +550,30 @@ private void PositionToplevel (Toplevel top)
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);
+
+ View superView = null;
+ StatusBar statusBar = null;
+
+ if (top != Application.Top && Application.Top.StatusBar != null) {
+ superView = Application.Top;
+ statusBar = Application.Top.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 >= superView.Frame.Height - (statusBar.Visible ? 1 : 0)) {
+ if (top.Height is Dim.DimFill) {
+ top.Height = Dim.Fill (statusBar.Visible ? 1 : 0);
+ }
+ }
+ if (superView == Application.Top) {
+ top.SetRelativeLayout (superView.Frame);
+ } else {
+ 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,29 +585,128 @@ private void PositionToplevel (Toplevel top)
///
public override void Redraw (Rect bounds)
{
- if (IsCurrentTop || this == Application.Top) {
- 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);
- Driver.SetAttribute (Colors.Base.Normal);
- PositionToplevels ();
+ if (!Visible) {
+ return;
+ }
- foreach (var view in Subviews) {
- if (view.Frame.IntersectsWith (bounds)) {
- view.SetNeedsLayout ();
- view.SetNeedsDisplay (view.Bounds);
+ 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);
+
+ if (LayoutStyle == LayoutStyle.Computed)
+ SetRelativeLayout (Bounds);
+ PositionToplevels ();
+ LayoutSubviews ();
+
+ if (this == Application.MdiTop) {
+ 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);
+ }
}
}
+ }
+
+ foreach (var view in Subviews) {
+ if (view.Frame.IntersectsWith (bounds) && !OutsideTopFrame (this)) {
+ view.SetNeedsLayout ();
+ view.SetNeedsDisplay (view.Bounds);
+ //view.Redraw (view.Bounds);
+ }
+ }
+
+ ClearLayoutNeeded ();
+ ClearNeedsDisplay ();
+ }
+
+ base.Redraw (Bounds);
+ }
+
+ 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;
+
+ ///
+ 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}");
- ClearLayoutNeeded ();
- ClearNeedsDisplay ();
+ // FIXED: optimize, only SetNeedsDisplay on the before/after regions.
+ SetNeedsDisplay ();
+ return true;
}
}
- base.Redraw (base.Bounds);
+ if (mouseEvent.Flags == MouseFlags.Button1Released && dragPosition.HasValue) {
+ Application.UngrabMouse ();
+ Driver.UncookMouse ();
+ dragPosition = null;
+ }
+
+ //System.Diagnostics.Debug.WriteLine (mouseEvent.ToString ());
+ return false;
}
///
@@ -484,5 +717,209 @@ public virtual void WillPresent ()
{
FocusFirst ();
}
+
+ ///
+ /// Move to the next Mdi child from the .
+ ///
+ public virtual void MoveNext ()
+ {
+ Application.MoveNext ();
+ }
+
+ ///
+ /// Move to the previous Mdi child from the .
+ ///
+ public virtual void MovePrevious ()
+ {
+ Application.MovePrevious ();
+ }
+
+ ///
+ /// Stops running this .
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Stops running the .
+ ///
+ /// The toplevel to request stop.
+ public virtual void RequestStop (Toplevel top)
+ {
+ top.RequestStop ();
+ }
+
+ ///
+ 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 ();
+ }
+
+ ///
+ /// Gets the current visible toplevel Mdi child that match the arguments pattern.
+ ///
+ /// The type.
+ /// The strings to exclude.
+ /// The matched view.
+ 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;
+ }
+
+ ///
+ /// Shows the Mdi child indicated by the setting as .
+ ///
+ /// The toplevel.
+ /// if the toplevel can be showed. otherwise.
+ public virtual bool ShowChild (Toplevel top = null)
+ {
+ if (Application.MdiTop != null) {
+ return Application.ShowChild (top == null ? this : top);
+ }
+ return false;
+ }
+ }
+
+ ///
+ /// Implements the to comparing two used by .
+ ///
+ public class ToplevelEqualityComparer : IEqualityComparer {
+ /// Determines whether the specified objects are equal.
+ /// The first object of type to compare.
+ /// The second object of type to compare.
+ ///
+ /// if the specified objects are equal; otherwise, .
+ 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;
+ }
+
+ /// Returns a hash code for the specified object.
+ /// The for which a hash code is to be returned.
+ /// A hash code for the specified object.
+ /// The type of is a reference type and is .
+ 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 ();
+ }
+ }
+
+ ///
+ /// Implements the to sort the from the if needed.
+ ///
+ public sealed class ToplevelComparer : IComparer {
+ /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
+ /// The first object to compare.
+ /// The second object to compare.
+ /// A signed integer that indicates the relative values of and , as shown in the following table.Value Meaning Less than zero
+ /// is less than .Zero
+ /// equals .Greater than zero
+ /// is greater than .
+ 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 ());
+ }
+ }
+ ///
+ /// implementation for the event.
+ ///
+ public class ToplevelClosingEventArgs : EventArgs {
+ ///
+ /// The toplevel requesting stop.
+ ///
+ public View RequestingTop { get; }
+ ///
+ /// Provides an event cancellation option.
+ ///
+ public bool Cancel { get; set; }
+
+ ///
+ /// Initializes the event arguments with the requesting toplevel.
+ ///
+ /// The .
+ public ToplevelClosingEventArgs (Toplevel requestingTop)
+ {
+ RequestingTop = requestingTop;
+ }
}
}
diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs
index f721feedf5..7d575c851e 100644
--- a/Terminal.Gui/Core/View.cs
+++ b/Terminal.Gui/Core/View.cs
@@ -268,7 +268,7 @@ public int TabIndex {
}
}
- private int GetTabIndex (int idx)
+ int GetTabIndex (int idx)
{
int i = 0;
foreach (var v in SuperView.tabIndexes) {
@@ -280,7 +280,7 @@ private int GetTabIndex (int idx)
return Math.Min (i, idx);
}
- private void SetTabIndex ()
+ void SetTabIndex ()
{
int i = 0;
foreach (var v in SuperView.tabIndexes) {
@@ -989,8 +989,8 @@ public void Clear (Rect regionScreen)
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;
@@ -1342,7 +1342,7 @@ public virtual void Redraw (Rect bounds)
Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal);
}
- if (!ustring.IsNullOrEmpty (Text)) {
+ if (!ustring.IsNullOrEmpty (Text) || (this is Label && !AutoSize)) {
Clear ();
// Draw any Text
if (textFormatter != null) {
@@ -1957,8 +1957,9 @@ void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)>
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;
diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs
index 3d91da8792..99734a6577 100644
--- a/Terminal.Gui/Core/Window.cs
+++ b/Terminal.Gui/Core/Window.cs
@@ -209,77 +209,6 @@ public override void Redraw (Rect bounds)
}
}
- //
- // FIXED:It does not look like the event is raised on clicked-drag
- // need to figure that out.
- //
- internal static Point? dragPosition;
- Point start;
-
- ///
- 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;
- }
-
///
/// The text displayed by the .
///
diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs
index a947d0836e..3a4d5099e6 100644
--- a/Terminal.Gui/Views/Menu.cs
+++ b/Terminal.Gui/Views/Menu.cs
@@ -166,7 +166,7 @@ public bool IsEnabled ()
///
/// Gets if this is from a sub-menu.
///
- internal bool IsFromSubMenu { get {return Parent != null; } }
+ internal bool IsFromSubMenu { get { return Parent != null; } }
///
/// Merely a debugging aid to see the interaction with main
@@ -274,7 +274,7 @@ public MenuBarItem (MenuItem [] children) : this ("", children) { }
///
/// Initializes a new .
///
- public MenuBarItem () : this (children: new MenuItem [] { }) { }
+ public MenuBarItem () : this (children: new MenuItem [] { }) { }
//static int GetMaxTitleLength (MenuItem [] children)
//{
@@ -447,7 +447,7 @@ public override void Redraw (Rect bounds)
for (int p = 0; p < Frame.Width - 2; p++)
if (item == null)
Driver.AddRune (Driver.HLine);
- else if (p == Frame.Width - 3 && barItems.SubMenu(barItems.Children [i]) != null)
+ else if (p == Frame.Width - 3 && barItems.SubMenu (barItems.Children [i]) != null)
Driver.AddRune (Driver.RightArrow);
else
Driver.AddRune (' ');
@@ -916,7 +916,7 @@ public override void Redraw (Rect bounds)
var menu = Menus [i];
Move (pos, 0);
Attribute hotColor, normalColor;
- if (i == selected) {
+ if (i == selected && IsMenuOpen) {
hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal;
} else if (openedByAltKey) {
@@ -966,7 +966,7 @@ void Selected (MenuItem item)
///
/// Raised as a menu is opening.
///
- public event Action MenuOpening;
+ public event Action MenuOpening;
///
/// Raised when a menu is closing.
@@ -986,11 +986,15 @@ void Selected (MenuItem item)
public bool IsMenuOpen { get; protected set; }
///
- /// Virtual method that will invoke the
+ /// Virtual method that will invoke the event if it's defined.
///
- public virtual void OnMenuOpening ()
+ /// The current menu to be replaced.
+ /// /// Returns the
+ public virtual MenuOpeningEventArgs OnMenuOpening (MenuBarItem currentMenu)
{
- MenuOpening?.Invoke ();
+ var ev = new MenuOpeningEventArgs (currentMenu);
+ MenuOpening?.Invoke (ev);
+ return ev;
}
///
@@ -1011,11 +1015,17 @@ public virtual void OnMenuClosing ()
internal void OpenMenu (int index, int sIndex = -1, MenuBarItem subMenu = null)
{
isMenuOpening = true;
- OnMenuOpening ();
+ var newMenu = OnMenuOpening (Menus [index]);
+ if (newMenu.Cancel) {
+ return;
+ }
+ if (newMenu.NewMenuBarItem != null && Menus [index].Title == newMenu.NewMenuBarItem.Title) {
+ Menus [index] = newMenu.NewMenuBarItem;
+ }
int pos = 0;
switch (subMenu) {
case null:
- lastFocused = lastFocused ?? SuperView.MostFocused;
+ lastFocused = lastFocused ?? SuperView?.MostFocused;
if (openSubMenu != null)
CloseMenu (false, true);
if (openMenu != null) {
@@ -1460,7 +1470,7 @@ public override bool ProcessKey (KeyEvent kb)
case Key.CursorDown:
case Key.Enter:
if (selected > -1) {
- ProcessMenu (selected, Menus [selected]);
+ ProcessMenu (selected, Menus [selected]);
}
break;
@@ -1638,4 +1648,32 @@ public override bool OnEnter (View view)
return base.OnEnter (view);
}
}
+
+ ///
+ /// An which allows passing a cancelable menu opening event or replacing with a new .
+ ///
+ public class MenuOpeningEventArgs : EventArgs {
+ ///
+ /// The current parent.
+ ///
+ public MenuBarItem CurrentMenu { get; }
+
+ ///
+ /// The new to be replaced.
+ ///
+ public MenuBarItem NewMenuBarItem { get; set; }
+ ///
+ /// Flag that allows you to cancel the opening of the menu.
+ ///
+ public bool Cancel { get; set; }
+
+ ///
+ /// Initializes a new instance of
+ ///
+ /// The current parent.
+ public MenuOpeningEventArgs (MenuBarItem currentMenu)
+ {
+ CurrentMenu = currentMenu;
+ }
+ }
}
diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs
index 4cea78d2ff..0cc2bf3d32 100644
--- a/UICatalog/Scenarios/AllViewsTester.cs
+++ b/UICatalog/Scenarios/AllViewsTester.cs
@@ -228,6 +228,8 @@ public override void Setup ()
Top.Add (_leftPane, _settingsPane, _hostPane);
+ Top.LayoutSubviews ();
+
_curView = CreateClass (_viewClasses.First ().Value);
}
@@ -236,7 +238,12 @@ void DimPosChanged (View view)
if (view == null) {
return;
}
+
+ var layout = view.LayoutStyle;
+
try {
+ view.LayoutStyle = LayoutStyle.Absolute;
+
switch (_xRadioGroup.SelectedItem) {
case 0:
view.X = Pos.Percent (_xVal);
@@ -292,6 +299,8 @@ void DimPosChanged (View view)
}
} catch (Exception e) {
MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
+ } finally {
+ view.LayoutStyle = layout;
}
UpdateTitle (view);
}
@@ -366,8 +375,10 @@ View CreateClass (Type type)
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/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
new file mode 100644
index 0000000000..0cc30f211e
--- /dev/null
+++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
@@ -0,0 +1,393 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Threading;
+using System.Threading.Tasks;
+using Terminal.Gui;
+
+namespace UICatalog {
+ [ScenarioMetadata (Name: "BackgroundWorker Collection", Description: "A persisting multi Toplevel BackgroundWorker threading")]
+ [ScenarioCategory ("Threading")]
+ [ScenarioCategory ("TopLevel")]
+ [ScenarioCategory ("Dialogs")]
+ [ScenarioCategory ("Controls")]
+ class BackgroundWorkerCollection : Scenario {
+ public override void Init (Toplevel top, ColorScheme colorScheme)
+ {
+ Application.Top.Dispose ();
+
+ Application.Run ();
+
+ Application.Top.Dispose ();
+ }
+
+ public override void Run ()
+ {
+ }
+
+ class MdiMain : Toplevel {
+ private WorkerApp workerApp;
+ private bool canOpenWorkerApp;
+ MenuBar menu;
+
+ public MdiMain ()
+ {
+ Data = "MdiMain";
+
+ IsMdiContainer = true;
+
+ workerApp = new WorkerApp () { Visible = false };
+
+ menu = new MenuBar (new MenuBarItem [] {
+ new MenuBarItem ("_Options", new MenuItem [] {
+ new MenuItem ("_Run Worker", "", () => workerApp.RunWorker(), null, null, Key.CtrlMask | Key.R),
+ new MenuItem ("_Cancel Worker", "", () => workerApp.CancelWorker(), null, null, Key.CtrlMask | Key.C),
+ null,
+ new MenuItem ("_Quit", "", () => Quit(), null, null, Key.CtrlMask | Key.Q)
+ }),
+ new MenuBarItem ("_View", new MenuItem [] { }),
+ new MenuBarItem ("_Window", new MenuItem [] { })
+ });
+ menu.MenuOpening += Menu_MenuOpening;
+ Add (menu);
+
+ var statusBar = new StatusBar (new [] {
+ new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Exit", () => Quit()),
+ new StatusItem(Key.CtrlMask | Key.R, "~^R~ Run Worker", () => workerApp.RunWorker()),
+ new StatusItem(Key.CtrlMask | Key.C, "~^C~ Cancel Worker", () => workerApp.CancelWorker())
+ });
+ Add (statusBar);
+
+ Activate += MdiMain_Activate;
+ Deactivate += MdiMain_Deactivate;
+
+ Closed += MdiMain_Closed;
+
+ Application.Iteration += () => {
+ if (canOpenWorkerApp && !workerApp.Running && Application.MdiTop.Running) {
+ Application.Run (workerApp);
+ }
+ };
+ }
+
+ private void MdiMain_Closed (Toplevel obj)
+ {
+ workerApp.Dispose ();
+ Dispose ();
+ }
+
+ private void Menu_MenuOpening (MenuOpeningEventArgs menu)
+ {
+ if (!canOpenWorkerApp) {
+ canOpenWorkerApp = true;
+ return;
+ }
+ if (menu.CurrentMenu.Title == "_Window") {
+ menu.NewMenuBarItem = OpenedWindows ();
+ } else if (menu.CurrentMenu.Title == "_View") {
+ menu.NewMenuBarItem = View ();
+ }
+ }
+
+ private void MdiMain_Deactivate (Toplevel top)
+ {
+ workerApp.WriteLog ($"{top.Data} deactivate.");
+ }
+
+ private void MdiMain_Activate (Toplevel top)
+ {
+ workerApp.WriteLog ($"{top.Data} activate.");
+ }
+
+ private MenuBarItem View ()
+ {
+ List