diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs
index d26dcd432..969d4c31e 100644
--- a/Terminal.Gui/Application/Application.Keyboard.cs
+++ b/Terminal.Gui/Application/Application.Keyboard.cs
@@ -267,34 +267,6 @@ public static partial class Application // Keyboard handling
CommandImplementations [command] = ctx => f ();
}
- /////
- ///// The key bindings.
- /////
- //private static readonly Dictionary> _keyBindings = new ();
-
- /////
- ///// Gets the list of key bindings.
- /////
- //public static Dictionary> GetKeyBindings () { return _keyBindings; }
-
- /////
- ///// Adds an scoped key binding.
- /////
- /////
- ///// This is an internal method used by the class to add Application key bindings.
- /////
- ///// The key being bound.
- ///// The view that is bound to the key. If , will be used.
- //internal static void AddKeyBinding (Key key, View? view)
- //{
- // if (!_keyBindings.ContainsKey (key))
- // {
- // _keyBindings [key] = [];
- // }
-
- // _keyBindings [key].Add (view);
- //}
-
internal static void AddApplicationKeyBindings ()
{
// Things this view knows how to do
@@ -326,7 +298,7 @@ public static partial class Application // Keyboard handling
);
AddCommand (
- Command.NextView,
+ Command.NextView,
() =>
{
// TODO: Move this method to Application.Navigation.cs
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
index e99521d1e..dc5c785a0 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
@@ -603,48 +603,6 @@ public abstract class ConsoleDriver
#endregion
}
-/// Terminal Cursor Visibility settings.
-///
-/// Hex value are set as 0xAABBCCDD where : AA stand for the TERMINFO DECSUSR parameter value to be used under
-/// Linux and MacOS BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS CC stand for the
-/// CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows DD stand for the CONSOLE_CURSOR_INFO.dwSize
-/// parameter value to be used under Windows
-///
-public enum CursorVisibility
-{
- /// Cursor caret has default
- ///
- /// Works under Xterm-like terminal otherwise this is equivalent to . This default directly
- /// depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking.
- ///
- Default = 0x00010119,
-
- /// Cursor caret is hidden
- Invisible = 0x03000019,
-
- /// Cursor caret is normally shown as a blinking underline bar _
- Underline = 0x03010119,
-
- /// Cursor caret is normally shown as a underline bar _
- /// Under Windows, this is equivalent to
- UnderlineFix = 0x04010119,
-
- /// Cursor caret is displayed a blinking vertical bar |
- /// Works under Xterm-like terminal otherwise this is equivalent to
- Vertical = 0x05010119,
-
- /// Cursor caret is displayed a blinking vertical bar |
- /// Works under Xterm-like terminal otherwise this is equivalent to
- VerticalFix = 0x06010119,
-
- /// Cursor caret is displayed as a blinking block ▉
- Box = 0x01020164,
-
- /// Cursor caret is displayed a block ▉
- /// Works under Xterm-like terminal otherwise this is equivalent to
- BoxFix = 0x02020164
-}
-
///
/// The enumeration encodes key information from s and provides a
/// consistent way for application code to specify keys and receive key events.
diff --git a/Terminal.Gui/ConsoleDrivers/CursorVisibility.cs b/Terminal.Gui/ConsoleDrivers/CursorVisibility.cs
new file mode 100644
index 000000000..b96d31fd4
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/CursorVisibility.cs
@@ -0,0 +1,44 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// Terminal Cursor Visibility settings.
+///
+/// Hex value are set as 0xAABBCCDD where : AA stand for the TERMINFO DECSUSR parameter value to be used under
+/// Linux and MacOS BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS CC stand for the
+/// CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows DD stand for the CONSOLE_CURSOR_INFO.dwSize
+/// parameter value to be used under Windows
+///
+public enum CursorVisibility
+{
+ /// Cursor caret has default
+ ///
+ /// Works under Xterm-like terminal otherwise this is equivalent to . This default directly
+ /// depends on the XTerm user configuration settings, so it could be Block, I-Beam, Underline with possible blinking.
+ ///
+ Default = 0x00010119,
+
+ /// Cursor caret is hidden
+ Invisible = 0x03000019,
+
+ /// Cursor caret is normally shown as a blinking underline bar _
+ Underline = 0x03010119,
+
+ /// Cursor caret is normally shown as a underline bar _
+ /// Under Windows, this is equivalent to
+ UnderlineFix = 0x04010119,
+
+ /// Cursor caret is displayed a blinking vertical bar |
+ /// Works under Xterm-like terminal otherwise this is equivalent to
+ Vertical = 0x05010119,
+
+ /// Cursor caret is displayed a blinking vertical bar |
+ /// Works under Xterm-like terminal otherwise this is equivalent to
+ VerticalFix = 0x06010119,
+
+ /// Cursor caret is displayed as a blinking block ▉
+ Box = 0x01020164,
+
+ /// Cursor caret is displayed a block ▉
+ /// Works under Xterm-like terminal otherwise this is equivalent to
+ BoxFix = 0x02020164
+}
diff --git a/Terminal.Gui/View/DrawEventArgs.cs b/Terminal.Gui/View/DrawEventArgs.cs
new file mode 100644
index 000000000..32c07c711
--- /dev/null
+++ b/Terminal.Gui/View/DrawEventArgs.cs
@@ -0,0 +1,29 @@
+namespace Terminal.Gui;
+
+/// Event args for draw events
+public class DrawEventArgs : EventArgs
+{
+ /// Creates a new instance of the class.
+ ///
+ /// The Content-relative rectangle describing the new visible viewport into the
+ /// .
+ ///
+ ///
+ /// The Content-relative rectangle describing the old visible viewport into the
+ /// .
+ ///
+ public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport)
+ {
+ NewViewport = newViewport;
+ OldViewport = oldViewport;
+ }
+
+ /// If set to true, the draw operation will be canceled, if applicable.
+ public bool Cancel { get; set; }
+
+ /// Gets the Content-relative rectangle describing the old visible viewport into the .
+ public Rectangle OldViewport { get; }
+
+ /// Gets the Content-relative rectangle describing the currently visible viewport into the .
+ public Rectangle NewViewport { get; }
+}
diff --git a/Terminal.Gui/View/Layout/LayoutEventArgs.cs b/Terminal.Gui/View/Layout/LayoutEventArgs.cs
new file mode 100644
index 000000000..dac959af0
--- /dev/null
+++ b/Terminal.Gui/View/Layout/LayoutEventArgs.cs
@@ -0,0 +1,12 @@
+namespace Terminal.Gui;
+
+/// Event arguments for the event.
+public class LayoutEventArgs : EventArgs
+{
+ /// Creates a new instance of the class.
+ /// The view that the event is about.
+ public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; }
+
+ /// The viewport of the before it was laid out.
+ public Size OldContentSize { get; set; }
+}
diff --git a/Terminal.Gui/View/Navigation/FocusEventArgs.cs b/Terminal.Gui/View/Navigation/FocusEventArgs.cs
new file mode 100644
index 000000000..6d8d28267
--- /dev/null
+++ b/Terminal.Gui/View/Navigation/FocusEventArgs.cs
@@ -0,0 +1,27 @@
+namespace Terminal.Gui;
+
+/// Defines the event arguments for
+public class FocusEventArgs : EventArgs
+{
+ /// Constructs.
+ /// The view that is losing focus.
+ /// The view that is gaining focus.
+ public FocusEventArgs (View leaving, View entering) {
+ Leaving = leaving;
+ Entering = entering;
+ }
+
+ ///
+ /// Indicates if the current focus event has already been processed and the driver should stop notifying any other
+ /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
+ /// subscriber method.
+ ///
+ public bool Handled { get; set; }
+
+ /// Indicates the view that is losing focus.
+ public View Leaving { get; set; }
+
+ /// Indicates the view that is gaining focus.
+ public View Entering { get; set; }
+
+}
diff --git a/Terminal.Gui/View/ViewAdornments.cs b/Terminal.Gui/View/View.Adornments.cs
similarity index 99%
rename from Terminal.Gui/View/ViewAdornments.cs
rename to Terminal.Gui/View/View.Adornments.cs
index accb15aba..2d179079e 100644
--- a/Terminal.Gui/View/ViewAdornments.cs
+++ b/Terminal.Gui/View/View.Adornments.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace Terminal.Gui;
-public partial class View
+public partial class View // Adornments
{
///
/// Initializes the Adornments of the View. Called by the constructor.
diff --git a/Terminal.Gui/View/View.Arrangement.cs b/Terminal.Gui/View/View.Arrangement.cs
new file mode 100644
index 000000000..0fea93324
--- /dev/null
+++ b/Terminal.Gui/View/View.Arrangement.cs
@@ -0,0 +1,15 @@
+namespace Terminal.Gui;
+
+public partial class View
+{
+ ///
+ /// Gets or sets the user actions that are enabled for the view within it's .
+ ///
+ ///
+ ///
+ /// Sizing or moving a view is only possible if the is part of a and
+ /// the relevant position and dimensions of the are independent of other SubViews
+ ///
+ ///
+ public ViewArrangement Arrangement { get; set; }
+}
diff --git a/Terminal.Gui/View/ViewContent.cs b/Terminal.Gui/View/View.Content.cs
similarity index 100%
rename from Terminal.Gui/View/ViewContent.cs
rename to Terminal.Gui/View/View.Content.cs
diff --git a/Terminal.Gui/View/View.Cursor.cs b/Terminal.Gui/View/View.Cursor.cs
new file mode 100644
index 000000000..bdba7d85f
--- /dev/null
+++ b/Terminal.Gui/View/View.Cursor.cs
@@ -0,0 +1,35 @@
+namespace Terminal.Gui;
+
+public partial class View
+{
+ ///
+ /// Gets or sets the cursor style to be used when the view is focused. The default is
+ /// .
+ ///
+ public CursorVisibility CursorVisibility { get; set; } = CursorVisibility.Invisible;
+
+ ///
+ /// Positions the cursor in the right position based on the currently focused view in the chain.
+ ///
+ ///
+ ///
+ /// Views that are focusable should override to make sure that the cursor is
+ /// placed in a location that makes sense. Some terminals do not have a way of hiding the cursor, so it can be
+ /// distracting to have the cursor left at the last focused view. So views should make sure that they place the
+ /// cursor in a visually sensible place. The default implementation of will place the
+ /// cursor at either the hotkey (if defined) or 0,0.
+ ///
+ ///
+ /// Viewport-relative cursor position. Return to ensure the cursor is not visible.
+ public virtual Point? PositionCursor ()
+ {
+ if (IsInitialized && CanFocus && HasFocus)
+ {
+ // By default, position the cursor at the hotkey (if any) or 0, 0.
+ Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
+ }
+
+ // Returning null will hide the cursor.
+ return null;
+ }
+}
diff --git a/Terminal.Gui/View/ViewDiagnostics.cs b/Terminal.Gui/View/View.Diagnostics.cs
similarity index 100%
rename from Terminal.Gui/View/ViewDiagnostics.cs
rename to Terminal.Gui/View/View.Diagnostics.cs
diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/View.Drawing.cs
similarity index 99%
rename from Terminal.Gui/View/ViewDrawing.cs
rename to Terminal.Gui/View/View.Drawing.cs
index 73fa5d550..1077f3917 100644
--- a/Terminal.Gui/View/ViewDrawing.cs
+++ b/Terminal.Gui/View/View.Drawing.cs
@@ -2,7 +2,7 @@
namespace Terminal.Gui;
-public partial class View
+public partial class View // Drawing APIs
{
private ColorScheme _colorScheme;
diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs
new file mode 100644
index 000000000..125baf33f
--- /dev/null
+++ b/Terminal.Gui/View/View.Hierarchy.cs
@@ -0,0 +1,320 @@
+namespace Terminal.Gui;
+
+public partial class View // SuperView/SubView hierarchy management (SuperView, SubViews, Add, Remove, etc.)
+{
+ private static readonly IList _empty = new List (0).AsReadOnly ();
+ internal bool _addingView;
+ private List _subviews; // This is null, and allocated on demand.
+ private View _superView;
+
+ /// Indicates whether the view was added to .
+ public bool IsAdded { get; private set; }
+
+ /// This returns a list of the subviews contained by this view.
+ /// The subviews.
+ public IList Subviews => _subviews?.AsReadOnly () ?? _empty;
+
+ /// Returns the container for this view, or null if this view has not been added to a container.
+ /// The super view.
+ public virtual View SuperView
+ {
+ get => _superView;
+ set => throw new NotImplementedException ();
+ }
+
+ // Internally, we use InternalSubviews rather than subviews, as we do not expect us
+ // to make the same mistakes our users make when they poke at the Subviews.
+ internal IList InternalSubviews => _subviews ?? _empty;
+
+ /// Adds a subview (child) to this view.
+ ///
+ ///
+ /// The Views that have been added to this view can be retrieved via the property. See also
+ ///
+ ///
+ ///
+ /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes
+ /// the lifecycle of the subviews to be transferred to this View.
+ ///
+ ///
+ /// The view to add.
+ /// The view that was added.
+ public virtual View Add (View view)
+ {
+ if (view is null)
+ {
+ return view;
+ }
+
+ if (_subviews is null)
+ {
+ _subviews = new ();
+ }
+
+ if (_tabIndexes is null)
+ {
+ _tabIndexes = new ();
+ }
+
+ _subviews.Add (view);
+ _tabIndexes.Add (view);
+ view._superView = this;
+
+ if (view.CanFocus)
+ {
+ _addingView = true;
+
+ if (SuperView?.CanFocus == false)
+ {
+ SuperView._addingView = true;
+ SuperView.CanFocus = true;
+ SuperView._addingView = false;
+ }
+
+ // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying.
+ CanFocus = true;
+ view._tabIndex = _tabIndexes.IndexOf (view);
+ _addingView = false;
+ }
+
+ if (view.Enabled && !Enabled)
+ {
+ view._oldEnabled = true;
+ view.Enabled = false;
+ }
+
+ OnAdded (new (this, view));
+
+ if (IsInitialized && !view.IsInitialized)
+ {
+ view.BeginInit ();
+ view.EndInit ();
+ }
+
+ CheckDimAuto ();
+ SetNeedsLayout ();
+ SetNeedsDisplay ();
+
+ return view;
+ }
+
+ /// Adds the specified views (children) to the view.
+ /// Array of one or more views (can be optional parameter).
+ ///
+ ///
+ /// The Views that have been added to this view can be retrieved via the property. See also
+ /// and .
+ ///
+ ///
+ /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes
+ /// the lifecycle of the subviews to be transferred to this View.
+ ///
+ ///
+ public void Add (params View [] views)
+ {
+ if (views is null)
+ {
+ return;
+ }
+
+ foreach (View view in views)
+ {
+ Add (view);
+ }
+ }
+
+ /// Event fired when this view is added to another.
+ public event EventHandler Added;
+
+ /// Get the top superview of a given .
+ /// The superview view.
+ public View GetTopSuperView (View view = null, View superview = null)
+ {
+ View top = superview ?? Application.Top;
+
+ for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
+ {
+ top = v;
+
+ if (top == superview)
+ {
+ break;
+ }
+ }
+
+ return top;
+ }
+
+ /// Method invoked when a subview is being added to this view.
+ /// Event where is the subview being added.
+ public virtual void OnAdded (SuperViewChangedEventArgs e)
+ {
+ View view = e.Child;
+ view.IsAdded = true;
+ view.OnResizeNeeded ();
+ view.Added?.Invoke (this, e);
+ }
+
+ /// Method invoked when a subview is being removed from this view.
+ /// Event args describing the subview being removed.
+ public virtual void OnRemoved (SuperViewChangedEventArgs e)
+ {
+ View view = e.Child;
+ view.IsAdded = false;
+ view.Removed?.Invoke (this, e);
+ }
+
+ /// Removes a subview added via or from this View.
+ ///
+ ///
+ /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
+ /// Subview's
+ /// lifecycle to be transferred to the caller; the caller muse call .
+ ///
+ ///
+ public virtual View Remove (View view)
+ {
+ if (view is null || _subviews is null)
+ {
+ return view;
+ }
+
+ Rectangle touched = view.Frame;
+ _subviews.Remove (view);
+ _tabIndexes.Remove (view);
+ view._superView = null;
+ view._tabIndex = -1;
+ SetNeedsLayout ();
+ SetNeedsDisplay ();
+
+ foreach (View v in _subviews)
+ {
+ if (v.Frame.IntersectsWith (touched))
+ {
+ view.SetNeedsDisplay ();
+ }
+ }
+
+ OnRemoved (new (this, view));
+
+ if (Focused == view)
+ {
+ Focused = null;
+ }
+
+ return view;
+ }
+
+ ///
+ /// Removes all subviews (children) added via or from this View.
+ ///
+ ///
+ ///
+ /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
+ /// Subview's
+ /// lifecycle to be transferred to the caller; the caller must call on any Views that were
+ /// added.
+ ///
+ ///
+ public virtual void RemoveAll ()
+ {
+ if (_subviews is null)
+ {
+ return;
+ }
+
+ while (_subviews.Count > 0)
+ {
+ Remove (_subviews [0]);
+ }
+ }
+
+ /// Event fired when this view is removed from another.
+ public event EventHandler Removed;
+
+
+ /// Moves one position towards the start of the list
+ /// The subview to move forward.
+ public void BringSubviewForward (View subview)
+ {
+ PerformActionForSubview (
+ subview,
+ x =>
+ {
+ int idx = _subviews.IndexOf (x);
+
+ if (idx + 1 < _subviews.Count)
+ {
+ _subviews.Remove (x);
+ _subviews.Insert (idx + 1, x);
+ }
+ }
+ );
+ }
+
+ /// Moves to the start of the list.
+ /// The subview to send to the start.
+ public void BringSubviewToFront (View subview)
+ {
+ PerformActionForSubview (
+ subview,
+ x =>
+ {
+ _subviews.Remove (x);
+ _subviews.Add (x);
+ }
+ );
+ }
+
+
+ /// Moves one position towards the end of the list
+ /// The subview to move backwards.
+ public void SendSubviewBackwards (View subview)
+ {
+ PerformActionForSubview (
+ subview,
+ x =>
+ {
+ int idx = _subviews.IndexOf (x);
+
+ if (idx > 0)
+ {
+ _subviews.Remove (x);
+ _subviews.Insert (idx - 1, x);
+ }
+ }
+ );
+ }
+
+ /// Moves to the end of the list.
+ /// The subview to send to the end.
+ public void SendSubviewToBack (View subview)
+ {
+ PerformActionForSubview (
+ subview,
+ x =>
+ {
+ _subviews.Remove (x);
+ _subviews.Insert (0, subview);
+ }
+ );
+ }
+
+ ///
+ /// Internal API that runs on a subview if it is part of the list.
+ ///
+ ///
+ ///
+ private void PerformActionForSubview (View subview, Action action)
+ {
+ if (_subviews.Contains (subview))
+ {
+ action (subview);
+ }
+
+ // BUGBUG: this is odd. Why is this needed?
+ SetNeedsDisplay ();
+ subview.SetNeedsDisplay ();
+ }
+
+}
diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/View.Keyboard.cs
similarity index 91%
rename from Terminal.Gui/View/ViewKeyboard.cs
rename to Terminal.Gui/View/View.Keyboard.cs
index 7a905f129..7009ab4c6 100644
--- a/Terminal.Gui/View/ViewKeyboard.cs
+++ b/Terminal.Gui/View/View.Keyboard.cs
@@ -3,7 +3,7 @@ using System.Diagnostics;
namespace Terminal.Gui;
-public partial class View
+public partial class View // Keyboard APIs
{
///
/// Helper to configure all things keyboard related for a View. Called from the View constructor.
@@ -254,119 +254,6 @@ public partial class View
#endregion HotKey Support
- #region Tab/Focus Handling
-
- // This is null, and allocated on demand.
- private List _tabIndexes;
-
- /// Gets a list of the subviews that are s.
- /// The tabIndexes.
- public IList TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
-
- private int _tabIndex = -1;
- private int _oldTabIndex;
-
- ///
- /// Indicates the index of the current from the list. See also:
- /// .
- ///
- public int TabIndex
- {
- get => _tabIndex;
- set
- {
- if (!CanFocus)
- {
- _tabIndex = -1;
-
- return;
- }
-
- if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1)
- {
- _tabIndex = 0;
-
- return;
- }
-
- if (_tabIndex == value && TabIndexes.IndexOf (this) == value)
- {
- return;
- }
-
- _tabIndex = value > SuperView._tabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 :
- value < 0 ? 0 : value;
- _tabIndex = GetTabIndex (_tabIndex);
-
- if (SuperView._tabIndexes.IndexOf (this) != _tabIndex)
- {
- SuperView._tabIndexes.Remove (this);
- SuperView._tabIndexes.Insert (_tabIndex, this);
- SetTabIndex ();
- }
- }
- }
-
- private int GetTabIndex (int idx)
- {
- var i = 0;
-
- foreach (View v in SuperView._tabIndexes)
- {
- if (v._tabIndex == -1 || v == this)
- {
- continue;
- }
-
- i++;
- }
-
- return Math.Min (i, idx);
- }
-
- private void SetTabIndex ()
- {
- var i = 0;
-
- foreach (View v in SuperView._tabIndexes)
- {
- if (v._tabIndex == -1)
- {
- continue;
- }
-
- v._tabIndex = i;
- i++;
- }
- }
-
- private bool _tabStop = true;
-
- ///
- /// Gets or sets whether the view is a stop-point for keyboard navigation of focus. Will be
- /// only if the is also . Set to to prevent the
- /// view from being a stop-point for keyboard navigation.
- ///
- ///
- /// The default keyboard navigation keys are Key.Tab and Key>Tab.WithShift. These can be changed by
- /// modifying the key bindings (see ) of the SuperView.
- ///
- public bool TabStop
- {
- get => _tabStop;
- set
- {
- if (_tabStop == value)
- {
- return;
- }
-
- _tabStop = CanFocus && value;
- }
- }
-
- #endregion Tab/Focus Handling
-
#region Low-level Key handling
#region Key Down Event
diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/View.Layout.cs
similarity index 99%
rename from Terminal.Gui/View/Layout/ViewLayout.cs
rename to Terminal.Gui/View/View.Layout.cs
index 72a9bf14c..deb7da682 100644
--- a/Terminal.Gui/View/Layout/ViewLayout.cs
+++ b/Terminal.Gui/View/View.Layout.cs
@@ -3,7 +3,7 @@ using System.Diagnostics;
namespace Terminal.Gui;
-public partial class View
+public partial class View // Layout APIs
{
#region Frame
diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/View.Mouse.cs
similarity index 99%
rename from Terminal.Gui/View/ViewMouse.cs
rename to Terminal.Gui/View/View.Mouse.cs
index 24314f583..5f1318e21 100644
--- a/Terminal.Gui/View/ViewMouse.cs
+++ b/Terminal.Gui/View/View.Mouse.cs
@@ -2,7 +2,7 @@
namespace Terminal.Gui;
-public partial class View
+public partial class View // Mouse APIs
{
[CanBeNull]
private ColorScheme _savedHighlightColorScheme;
diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs
new file mode 100644
index 000000000..4ff99c3d7
--- /dev/null
+++ b/Terminal.Gui/View/View.Navigation.cs
@@ -0,0 +1,813 @@
+namespace Terminal.Gui;
+
+public partial class View // Focus and cross-view navigation management (TabStop, TabIndex, etc...)
+{
+ /// Returns a value indicating if this View is currently on Top (Active)
+ public bool IsCurrentTop => Application.Current == this;
+
+ // BUGBUG: This API is poorly defined and implemented. It deeply intertwines the view hierarchy with the tab order.
+ /// Exposed as `internal` for unit tests. Indicates focus navigation direction.
+ internal enum NavigationDirection
+ {
+ /// Navigate forward.
+ Forward,
+
+ /// Navigate backwards.
+ Backward
+ }
+
+ /// Invoked when this view is gaining focus (entering).
+ /// The view that is leaving focus.
+ /// , if the event was handled, otherwise.
+ ///
+ ///
+ /// Overrides must call the base class method to ensure that the event is raised. If the event
+ /// is handled, the method should return .
+ ///
+ ///
+ public virtual bool OnEnter (View leavingView)
+ {
+ var args = new FocusEventArgs (leavingView, this);
+ Enter?.Invoke (this, args);
+
+ if (args.Handled)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /// Invoked when this view is losing focus (leaving).
+ /// The view that is entering focus.
+ /// , if the event was handled, otherwise.
+ ///
+ ///
+ /// Overrides must call the base class method to ensure that the event is raised. If the event
+ /// is handled, the method should return .
+ ///
+ ///
+ public virtual bool OnLeave (View enteringView)
+ {
+ var args = new FocusEventArgs (this, enteringView);
+ Leave?.Invoke (this, args);
+
+ if (args.Handled)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /// Raised when the view is gaining (entering) focus. Can be cancelled.
+ ///
+ /// Raised by the virtual method.
+ ///
+ public event EventHandler Enter;
+
+ /// Raised when the view is losing (leaving) focus. Can be cancelled.
+ ///
+ /// Raised by the virtual method.
+ ///
+ public event EventHandler Leave;
+
+ private NavigationDirection _focusDirection;
+
+ ///
+ /// Gets or sets the focus direction for this view and all subviews.
+ /// Setting this property will set the focus direction for all views up the SuperView hierarchy.
+ ///
+ internal NavigationDirection FocusDirection
+ {
+ get => SuperView?.FocusDirection ?? _focusDirection;
+ set
+ {
+ if (SuperView is { })
+ {
+ SuperView.FocusDirection = value;
+ }
+ else
+ {
+ _focusDirection = value;
+ }
+ }
+ }
+
+ private bool _hasFocus;
+
+ ///
+ /// Gets or sets whether this view has focus.
+ ///
+ ///
+ ///
+ /// Causes the and virtual methods (and and
+ /// events to be raised) when the value changes.
+ ///
+ ///
+ /// Setting this property to will recursively set to
+ ///
+ /// for any focused subviews.
+ ///
+ ///
+ public bool HasFocus
+ {
+ // Force the specified view to have focus
+ set => SetHasFocus (value, this, true);
+ get => _hasFocus;
+ }
+
+ ///
+ /// Internal API that sets . This method is called by HasFocus_set and other methods that
+ /// need to set or remove focus from a view.
+ ///
+ /// The new setting for .
+ /// The view that will be gaining or losing focus.
+ ///
+ /// to force Enter/Leave on regardless of whether it
+ /// already HasFocus or not.
+ ///
+ ///
+ /// If is and there is a focused subview (
+ /// is not ),
+ /// this method will recursively remove focus from any focused subviews of .
+ ///
+ private void SetHasFocus (bool newHasFocus, View view, bool force = false)
+ {
+ if (HasFocus != newHasFocus || force)
+ {
+ _hasFocus = newHasFocus;
+
+ if (newHasFocus)
+ {
+ OnEnter (view);
+ }
+ else
+ {
+ OnLeave (view);
+ }
+
+ SetNeedsDisplay ();
+ }
+
+ // Remove focus down the chain of subviews if focus is removed
+ if (!newHasFocus && Focused is { })
+ {
+ View f = Focused;
+ f.OnLeave (view);
+ f.SetHasFocus (false, view);
+ Focused = null;
+ }
+ }
+
+ /// Raised when has been changed.
+ ///
+ /// Raised by the virtual method.
+ ///
+ public event EventHandler CanFocusChanged;
+
+ /// Invoked when the property from a view is changed.
+ ///
+ /// Raises the event.
+ ///
+ public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); }
+
+ private bool _oldCanFocus;
+ private bool _canFocus;
+
+ /// Gets or sets a value indicating whether this can be focused.
+ ///
+ ///
+ /// must also have set to .
+ ///
+ ///
+ public bool CanFocus
+ {
+ get => _canFocus;
+ set
+ {
+ if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value)
+ {
+ throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+ }
+
+ if (_canFocus == value)
+ {
+ return;
+ }
+
+ _canFocus = value;
+
+ switch (_canFocus)
+ {
+ case false when _tabIndex > -1:
+ TabIndex = -1;
+
+ break;
+ case true when SuperView?.CanFocus == false && _addingView:
+ SuperView.CanFocus = true;
+
+ break;
+ }
+
+ if (_canFocus && _tabIndex == -1)
+ {
+ TabIndex = SuperView is { } ? SuperView._tabIndexes.IndexOf (this) : -1;
+ }
+
+ TabStop = _canFocus;
+
+ if (!_canFocus && SuperView?.Focused == this)
+ {
+ SuperView.Focused = null;
+ }
+
+ if (!_canFocus && HasFocus)
+ {
+ SetHasFocus (false, this);
+ SuperView?.EnsureFocus ();
+
+ if (SuperView is { Focused: null })
+ {
+ SuperView.FocusNext ();
+
+ if (SuperView.Focused is null && Application.Current is { })
+ {
+ Application.Current.FocusNext ();
+ }
+
+ ApplicationOverlapped.BringOverlappedTopToFront ();
+ }
+ }
+
+ if (_subviews is { } && IsInitialized)
+ {
+ foreach (View view in _subviews)
+ {
+ if (view.CanFocus != value)
+ {
+ if (!value)
+ {
+ view._oldCanFocus = view.CanFocus;
+ view._oldTabIndex = view._tabIndex;
+ view.CanFocus = false;
+ view._tabIndex = -1;
+ }
+ else
+ {
+ if (_addingView)
+ {
+ view._addingView = true;
+ }
+
+ view.CanFocus = view._oldCanFocus;
+ view._tabIndex = view._oldTabIndex;
+ view._addingView = false;
+ }
+ }
+ }
+
+ if (this is Toplevel && Application.Current.Focused != this)
+ {
+ ApplicationOverlapped.BringOverlappedTopToFront ();
+ }
+ }
+
+ OnCanFocusChanged ();
+ SetNeedsDisplay ();
+ }
+ }
+
+ /// Returns the currently focused Subview inside this view, or if nothing is focused.
+ /// The currently focused Subview.
+ public View Focused { get; private set; }
+
+ ///
+ /// Returns the most focused Subview in the chain of subviews (the leaf view that has the focus), or
+ /// if nothing is focused.
+ ///
+ /// The most focused Subview.
+ public View MostFocused
+ {
+ get
+ {
+ if (Focused is null)
+ {
+ return null;
+ }
+
+ View most = Focused.MostFocused;
+
+ if (most is { })
+ {
+ return most;
+ }
+
+ return Focused;
+ }
+ }
+
+ /// Causes subview specified by to enter focus.
+ /// View.
+ private void SetFocus (View view)
+ {
+ if (view is null)
+ {
+ return;
+ }
+
+ //Console.WriteLine ($"Request to focus {view}");
+ if (!view.CanFocus || !view.Visible || !view.Enabled)
+ {
+ return;
+ }
+
+ if (Focused?._hasFocus == true && Focused == view)
+ {
+ return;
+ }
+
+ if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this)
+ {
+ if (!view._hasFocus)
+ {
+ view._hasFocus = true;
+ }
+
+ return;
+ }
+
+ // Make sure that this view is a subview
+ View c;
+
+ for (c = view._superView; c != null; c = c._superView)
+ {
+ if (c == this)
+ {
+ break;
+ }
+ }
+
+ if (c is null)
+ {
+ throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+ }
+
+ if (Focused is { })
+ {
+ Focused.SetHasFocus (false, view);
+ }
+
+ View f = Focused;
+ Focused = view;
+ Focused.SetHasFocus (true, f);
+ Focused.EnsureFocus ();
+
+ // Send focus upwards
+ if (SuperView is { })
+ {
+ SuperView.SetFocus (this);
+ }
+ else
+ {
+ SetFocus (this);
+ }
+ }
+
+ /// Causes this view to be focused and entire Superview hierarchy to have the focused order updated.
+ public void SetFocus ()
+ {
+ if (!CanBeVisible (this) || !Enabled)
+ {
+ if (HasFocus)
+ {
+ SetHasFocus (false, this);
+ }
+
+ return;
+ }
+
+ if (SuperView is { })
+ {
+ SuperView.SetFocus (this);
+ }
+ else
+ {
+ SetFocus (this);
+ }
+ }
+
+ ///
+ /// If there is no focused subview, calls or based on
+ /// .
+ /// does nothing.
+ ///
+ public void EnsureFocus ()
+ {
+ if (Focused is null && _subviews?.Count > 0)
+ {
+ if (FocusDirection == NavigationDirection.Forward)
+ {
+ FocusFirst ();
+ }
+ else
+ {
+ FocusLast ();
+ }
+ }
+ }
+
+ ///
+ /// Focuses the last focusable view in if one exists. If there are no views in
+ /// then the focus is set to the view itself.
+ ///
+ public void FocusFirst (bool overlapped = false)
+ {
+ if (!CanBeVisible (this))
+ {
+ return;
+ }
+
+ if (_tabIndexes is null)
+ {
+ SuperView?.SetFocus (this);
+
+ return;
+ }
+
+ foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)))
+ {
+ if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
+ {
+ SetFocus (view);
+
+ return;
+ }
+ }
+ }
+
+ ///
+ /// Focuses the last focusable view in if one exists. If there are no views in
+ /// then the focus is set to the view itself.
+ ///
+ public void FocusLast (bool overlapped = false)
+ {
+ if (!CanBeVisible (this))
+ {
+ return;
+ }
+
+ if (_tabIndexes is null)
+ {
+ SuperView?.SetFocus (this);
+
+ return;
+ }
+
+ foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ())
+ {
+ if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
+ {
+ SetFocus (view);
+
+ return;
+ }
+ }
+ }
+
+ ///
+ /// Focuses the previous view in . If there is no previous view, the focus is set to the
+ /// view itself.
+ ///
+ /// if previous was focused, otherwise.
+ public bool FocusPrev ()
+ {
+ if (!CanBeVisible (this))
+ {
+ return false;
+ }
+
+ FocusDirection = NavigationDirection.Backward;
+
+ if (TabIndexes is null || TabIndexes.Count == 0)
+ {
+ return false;
+ }
+
+ if (Focused is null)
+ {
+ FocusLast ();
+
+ return Focused != null;
+ }
+
+ int focusedIdx = -1;
+
+ for (int i = TabIndexes.Count; i > 0;)
+ {
+ i--;
+ View w = TabIndexes [i];
+
+ if (w.HasFocus)
+ {
+ if (w.FocusPrev ())
+ {
+ return true;
+ }
+
+ focusedIdx = i;
+
+ continue;
+ }
+
+ if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
+ {
+ Focused.SetHasFocus (false, w);
+
+ // If the focused view is overlapped don't focus on the next if it's not overlapped.
+ if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+ {
+ FocusLast (true);
+
+ return true;
+ }
+
+ // If the focused view is not overlapped and the next is, skip it
+ if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+ {
+ continue;
+ }
+
+ if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+ {
+ w.FocusLast ();
+ }
+
+ SetFocus (w);
+
+ return true;
+ }
+ }
+
+ // There's no prev view in tab indexes.
+ if (Focused is { })
+ {
+ // Leave Focused
+ Focused.SetHasFocus (false, this);
+
+ if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
+ {
+ FocusLast (true);
+
+ return true;
+ }
+
+ // Signal to caller no next view was found
+ Focused = null;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Focuses the next view in . If there is no next view, the focus is set to the view
+ /// itself.
+ ///
+ /// if next was focused, otherwise.
+ public bool FocusNext ()
+ {
+ if (!CanBeVisible (this))
+ {
+ return false;
+ }
+
+ FocusDirection = NavigationDirection.Forward;
+
+ if (TabIndexes is null || TabIndexes.Count == 0)
+ {
+ return false;
+ }
+
+ if (Focused is null)
+ {
+ FocusFirst ();
+
+ return Focused != null;
+ }
+
+ int focusedIdx = -1;
+
+ for (var i = 0; i < TabIndexes.Count; i++)
+ {
+ View w = TabIndexes [i];
+
+ if (w.HasFocus)
+ {
+ if (w.FocusNext ())
+ {
+ return true;
+ }
+
+ focusedIdx = i;
+
+ continue;
+ }
+
+ if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
+ {
+ Focused.SetHasFocus (false, w);
+
+ //// If the focused view is overlapped don't focus on the next if it's not overlapped.
+ //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped)/* && !w.Arrangement.HasFlag (ViewArrangement.Overlapped)*/)
+ //{
+ // return false;
+ //}
+
+ //// If the focused view is not overlapped and the next is, skip it
+ //if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
+ //{
+ // continue;
+ //}
+
+ if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
+ {
+ w.FocusFirst ();
+ }
+
+ SetFocus (w);
+
+ return true;
+ }
+ }
+
+ // There's no next view in tab indexes.
+ if (Focused is { })
+ {
+ // Leave Focused
+ Focused.SetHasFocus (false, this);
+
+ //if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
+ //{
+ // FocusFirst (true);
+ // return true;
+ //}
+
+ // Signal to caller no next view was found
+ Focused = null;
+ }
+
+ return false;
+ }
+
+ private View GetMostFocused (View view)
+ {
+ if (view is null)
+ {
+ return null;
+ }
+
+ return view.Focused is { } ? GetMostFocused (view.Focused) : view;
+ }
+
+ #region Tab/Focus Handling
+
+ private List _tabIndexes;
+
+ // TODO: This should be a get-only property?
+ // BUGBUG: This returns an AsReadOnly list, but isn't declared as such.
+ /// Gets a list of the subviews that are a .
+ /// The tabIndexes.
+ public IList TabIndexes => _tabIndexes?.AsReadOnly () ?? _empty;
+
+ // TODO: Change this to int? and use null to indicate the view is not in the tab order.
+ private int _tabIndex = -1;
+ private int _oldTabIndex;
+
+ ///
+ /// Indicates the index of the current from the list. See also:
+ /// .
+ ///
+ ///
+ ///
+ /// If the value is -1, the view is not part of the tab order.
+ ///
+ ///
+ /// On set, if is , will be set to -1.
+ ///
+ ///
+ /// On set, if is or has not TabStops, will
+ /// be set to 0.
+ ///
+ ///
+ /// On set, if has only one TabStop, will be set to 0.
+ ///
+ ///
+ public int TabIndex
+ {
+ get => _tabIndex;
+ set
+ {
+ if (!CanFocus)
+ {
+ // BUGBUG: Property setters should set the property to the value passed in and not have side effects.
+ _tabIndex = -1;
+
+ return;
+ }
+
+ if (SuperView?._tabIndexes is null || SuperView?._tabIndexes.Count == 1)
+ {
+ // BUGBUG: Property setters should set the property to the value passed in and not have side effects.
+ _tabIndex = 0;
+
+ return;
+ }
+
+ if (_tabIndex == value && TabIndexes.IndexOf (this) == value)
+ {
+ return;
+ }
+
+ _tabIndex = value > SuperView!.TabIndexes.Count - 1 ? SuperView._tabIndexes.Count - 1 :
+ value < 0 ? 0 : value;
+ _tabIndex = GetGreatestTabIndexInSuperView (_tabIndex);
+
+ if (SuperView._tabIndexes.IndexOf (this) != _tabIndex)
+ {
+ // BUGBUG: we have to use _tabIndexes and not TabIndexes because TabIndexes returns is a read-only version of _tabIndexes
+ SuperView._tabIndexes.Remove (this);
+ SuperView._tabIndexes.Insert (_tabIndex, this);
+ ReorderSuperViewTabIndexes ();
+ }
+ }
+ }
+
+ ///
+ /// Gets the greatest of the 's that is less
+ /// than or equal to .
+ ///
+ ///
+ /// The minimum of and the 's .
+ private int GetGreatestTabIndexInSuperView (int idx)
+ {
+ var i = 0;
+
+ foreach (View superViewTabStop in SuperView._tabIndexes)
+ {
+ if (superViewTabStop._tabIndex == -1 || superViewTabStop == this)
+ {
+ continue;
+ }
+
+ i++;
+ }
+
+ return Math.Min (i, idx);
+ }
+
+ ///
+ /// Re-orders the s of the views in the 's .
+ ///
+ private void ReorderSuperViewTabIndexes ()
+ {
+ var i = 0;
+
+ foreach (View superViewTabStop in SuperView._tabIndexes)
+ {
+ if (superViewTabStop._tabIndex == -1)
+ {
+ continue;
+ }
+
+ superViewTabStop._tabIndex = i;
+ i++;
+ }
+ }
+
+ private bool _tabStop = true;
+
+ ///
+ /// Gets or sets whether the view is a stop-point for keyboard navigation of focus. Will be
+ /// only if is . Set to to prevent the
+ /// view from being a stop-point for keyboard navigation.
+ ///
+ ///
+ /// The default keyboard navigation keys are Key.Tab and Key>Tab.WithShift. These can be changed by
+ /// modifying the key bindings (see ) of the SuperView.
+ ///
+ public bool TabStop
+ {
+ get => _tabStop;
+ set
+ {
+ if (_tabStop == value)
+ {
+ return;
+ }
+
+ _tabStop = CanFocus && value;
+ }
+ }
+
+ #endregion Tab/Focus Handling
+}
diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/View.Text.cs
similarity index 99%
rename from Terminal.Gui/View/ViewText.cs
rename to Terminal.Gui/View/View.Text.cs
index 6b9e0cf94..664640730 100644
--- a/Terminal.Gui/View/ViewText.cs
+++ b/Terminal.Gui/View/View.Text.cs
@@ -2,7 +2,7 @@
namespace Terminal.Gui;
-public partial class View
+public partial class View // Text Property APIs
{
///
/// Initializes the Text of the View. Called by the constructor.
diff --git a/Terminal.Gui/View/ViewArrangement.cs b/Terminal.Gui/View/ViewArrangement.cs
index df13c4762..0143b082e 100644
--- a/Terminal.Gui/View/ViewArrangement.cs
+++ b/Terminal.Gui/View/ViewArrangement.cs
@@ -1,14 +1,16 @@
namespace Terminal.Gui;
///
-/// Describes what user actions are enabled for arranging a within it's .
+/// Describes what user actions are enabled for arranging a within it's
+/// .
/// See .
///
///
-///
-/// Sizing or moving a view is only possible if the is part of a and
-/// the relevant position and dimensions of the are independent of other SubViews
-///
+///
+/// Sizing or moving a view is only possible if the is part of a
+/// and
+/// the relevant position and dimensions of the are independent of other SubViews
+///
///
[Flags]
public enum ViewArrangement
@@ -56,26 +58,14 @@ public enum ViewArrangement
Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable,
///
- /// The view overlap other views.
+ /// The view overlap other views.
///
///
///
- /// When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to the next/prev view in the next/prev Tabindex).
+ /// When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to
+ /// the next/prev view in the next/prev Tabindex).
/// Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views.
///
///
Overlapped = 32
}
-public partial class View
-{
- ///
- /// Gets or sets the user actions that are enabled for the view within it's .
- ///
- ///
- ///
- /// Sizing or moving a view is only possible if the is part of a and
- /// the relevant position and dimensions of the are independent of other SubViews
- ///
- ///
- public ViewArrangement Arrangement { get; set; }
-}
diff --git a/Terminal.Gui/View/ViewEventArgs.cs b/Terminal.Gui/View/ViewEventArgs.cs
index b17b98afe..cdcbaa009 100644
--- a/Terminal.Gui/View/ViewEventArgs.cs
+++ b/Terminal.Gui/View/ViewEventArgs.cs
@@ -13,69 +13,4 @@ public class ViewEventArgs : EventArgs
/// child then sender may be the parent while is the child being added.
///
public View View { get; }
-}
-
-/// Event arguments for the event.
-public class LayoutEventArgs : EventArgs
-{
- /// Creates a new instance of the class.
- /// The view that the event is about.
- public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; }
-
- /// The viewport of the before it was laid out.
- public Size OldContentSize { get; set; }
-}
-
-/// Event args for draw events
-public class DrawEventArgs : EventArgs
-{
- /// Creates a new instance of the class.
- ///
- /// The Content-relative rectangle describing the new visible viewport into the
- /// .
- ///
- ///
- /// The Content-relative rectangle describing the old visible viewport into the
- /// .
- ///
- public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport)
- {
- NewViewport = newViewport;
- OldViewport = oldViewport;
- }
-
- /// If set to true, the draw operation will be canceled, if applicable.
- public bool Cancel { get; set; }
-
- /// Gets the Content-relative rectangle describing the old visible viewport into the .
- public Rectangle OldViewport { get; }
-
- /// Gets the Content-relative rectangle describing the currently visible viewport into the .
- public Rectangle NewViewport { get; }
-}
-
-/// Defines the event arguments for
-public class FocusEventArgs : EventArgs
-{
- /// Constructs.
- /// The view that is losing focus.
- /// The view that is gaining focus.
- public FocusEventArgs (View leaving, View entering) {
- Leaving = leaving;
- Entering = entering;
- }
-
- ///
- /// Indicates if the current focus event has already been processed and the driver should stop notifying any other
- /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
- /// subscriber method.
- ///
- public bool Handled { get; set; }
-
- /// Indicates the view that is losing focus.
- public View Leaving { get; set; }
-
- /// Indicates the view that is gaining focus.
- public View Entering { get; set; }
-
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs
deleted file mode 100644
index 79cca431e..000000000
--- a/Terminal.Gui/View/ViewSubViews.cs
+++ /dev/null
@@ -1,948 +0,0 @@
-using System.Diagnostics;
-
-namespace Terminal.Gui;
-
-public partial class View
-{
- private static readonly IList _empty = new List (0).AsReadOnly ();
- internal bool _addingView;
- private List _subviews; // This is null, and allocated on demand.
- private View _superView;
-
- /// Indicates whether the view was added to .
- public bool IsAdded { get; private set; }
-
- /// Returns a value indicating if this View is currently on Top (Active)
- public bool IsCurrentTop => Application.Current == this;
-
- /// This returns a list of the subviews contained by this view.
- /// The subviews.
- public IList Subviews => _subviews?.AsReadOnly () ?? _empty;
-
- /// Returns the container for this view, or null if this view has not been added to a container.
- /// The super view.
- public virtual View SuperView
- {
- get => _superView;
- set => throw new NotImplementedException ();
- }
-
- // Internally, we use InternalSubviews rather than subviews, as we do not expect us
- // to make the same mistakes our users make when they poke at the Subviews.
- internal IList InternalSubviews => _subviews ?? _empty;
-
- /// Adds a subview (child) to this view.
- ///
- ///
- /// The Views that have been added to this view can be retrieved via the property. See also
- ///
- ///
- ///
- /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes
- /// the lifecycle of the subviews to be transferred to this View.
- ///
- ///
- /// The view to add.
- /// The view that was added.
- public virtual View Add (View view)
- {
- if (view is null)
- {
- return view;
- }
-
- if (_subviews is null)
- {
- _subviews = new ();
- }
-
- if (_tabIndexes is null)
- {
- _tabIndexes = new ();
- }
-
- _subviews.Add (view);
- _tabIndexes.Add (view);
- view._superView = this;
-
- if (view.CanFocus)
- {
- _addingView = true;
-
- if (SuperView?.CanFocus == false)
- {
- SuperView._addingView = true;
- SuperView.CanFocus = true;
- SuperView._addingView = false;
- }
-
- // QUESTION: This automatic behavior of setting CanFocus to true on the SuperView is not documented, and is annoying.
- CanFocus = true;
- view._tabIndex = _tabIndexes.IndexOf (view);
- _addingView = false;
- }
-
- if (view.Enabled && !Enabled)
- {
- view._oldEnabled = true;
- view.Enabled = false;
- }
-
- OnAdded (new (this, view));
-
- if (IsInitialized && !view.IsInitialized)
- {
- view.BeginInit ();
- view.EndInit ();
- }
-
- CheckDimAuto ();
- SetNeedsLayout ();
- SetNeedsDisplay ();
-
- return view;
- }
-
- /// Adds the specified views (children) to the view.
- /// Array of one or more views (can be optional parameter).
- ///
- ///
- /// The Views that have been added to this view can be retrieved via the property. See also
- /// and .
- ///
- ///
- /// Subviews will be disposed when this View is disposed. In other-words, calling this method causes
- /// the lifecycle of the subviews to be transferred to this View.
- ///
- ///
- public void Add (params View [] views)
- {
- if (views is null)
- {
- return;
- }
-
- foreach (View view in views)
- {
- Add (view);
- }
- }
-
- /// Event fired when this view is added to another.
- public event EventHandler Added;
-
- /// Moves the subview backwards in the hierarchy, only one step
- /// The subview to send backwards
- /// If you want to send the view all the way to the back use SendSubviewToBack.
- public void BringSubviewForward (View subview)
- {
- PerformActionForSubview (
- subview,
- x =>
- {
- int idx = _subviews.IndexOf (x);
-
- if (idx + 1 < _subviews.Count)
- {
- _subviews.Remove (x);
- _subviews.Insert (idx + 1, x);
- }
- }
- );
- }
-
- /// Brings the specified subview to the front so it is drawn on top of any other views.
- /// The subview to send to the front
- /// .
- public void BringSubviewToFront (View subview)
- {
- PerformActionForSubview (
- subview,
- x =>
- {
- _subviews.Remove (x);
- _subviews.Add (x);
- }
- );
- }
-
- /// Get the top superview of a given .
- /// The superview view.
- public View GetTopSuperView (View view = null, View superview = null)
- {
- View top = superview ?? Application.Top;
-
- for (View v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView)
- {
- top = v;
-
- if (top == superview)
- {
- break;
- }
- }
-
- return top;
- }
-
- /// Method invoked when a subview is being added to this view.
- /// Event where is the subview being added.
- public virtual void OnAdded (SuperViewChangedEventArgs e)
- {
- View view = e.Child;
- view.IsAdded = true;
- view.OnResizeNeeded ();
- view.Added?.Invoke (this, e);
- }
-
- /// Method invoked when a subview is being removed from this view.
- /// Event args describing the subview being removed.
- public virtual void OnRemoved (SuperViewChangedEventArgs e)
- {
- View view = e.Child;
- view.IsAdded = false;
- view.Removed?.Invoke (this, e);
- }
-
- /// Removes a subview added via or from this View.
- ///
- ///
- /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
- /// Subview's
- /// lifecycle to be transferred to the caller; the caller muse call .
- ///
- ///
- public virtual View Remove (View view)
- {
- if (view is null || _subviews is null)
- {
- return view;
- }
-
- Rectangle touched = view.Frame;
- _subviews.Remove (view);
- _tabIndexes.Remove (view);
- view._superView = null;
- view._tabIndex = -1;
- SetNeedsLayout ();
- SetNeedsDisplay ();
-
- foreach (View v in _subviews)
- {
- if (v.Frame.IntersectsWith (touched))
- {
- view.SetNeedsDisplay ();
- }
- }
-
- OnRemoved (new (this, view));
-
- if (Focused == view)
- {
- Focused = null;
- }
-
- return view;
- }
-
- ///
- /// Removes all subviews (children) added via or from this View.
- ///
- ///
- ///
- /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the
- /// Subview's
- /// lifecycle to be transferred to the caller; the caller must call on any Views that were
- /// added.
- ///
- ///
- public virtual void RemoveAll ()
- {
- if (_subviews is null)
- {
- return;
- }
-
- while (_subviews.Count > 0)
- {
- Remove (_subviews [0]);
- }
- }
-
- /// Event fired when this view is removed from another.
- public event EventHandler Removed;
-
- /// Moves the subview backwards in the hierarchy, only one step
- /// The subview to send backwards
- /// If you want to send the view all the way to the back use SendSubviewToBack.
- public void SendSubviewBackwards (View subview)
- {
- PerformActionForSubview (
- subview,
- x =>
- {
- int idx = _subviews.IndexOf (x);
-
- if (idx > 0)
- {
- _subviews.Remove (x);
- _subviews.Insert (idx - 1, x);
- }
- }
- );
- }
-
- /// Sends the specified subview to the front so it is the first view drawn
- /// The subview to send to the front
- /// .
- public void SendSubviewToBack (View subview)
- {
- PerformActionForSubview (
- subview,
- x =>
- {
- _subviews.Remove (x);
- _subviews.Insert (0, subview);
- }
- );
- }
-
- private void PerformActionForSubview (View subview, Action action)
- {
- if (_subviews.Contains (subview))
- {
- action (subview);
- }
-
- SetNeedsDisplay ();
- subview.SetNeedsDisplay ();
- }
-
- #region Focus
-
- /// Exposed as `internal` for unit tests. Indicates focus navigation direction.
- internal enum NavigationDirection
- {
- /// Navigate forward.
- Forward,
-
- /// Navigate backwards.
- Backward
- }
-
- /// Event fired when the view gets focus.
- public event EventHandler Enter;
-
- /// Event fired when the view looses focus.
- public event EventHandler Leave;
-
- private NavigationDirection _focusDirection;
-
- internal NavigationDirection FocusDirection
- {
- get => SuperView?.FocusDirection ?? _focusDirection;
- set
- {
- if (SuperView is { })
- {
- SuperView.FocusDirection = value;
- }
- else
- {
- _focusDirection = value;
- }
- }
- }
-
- private bool _hasFocus;
-
- ///
- public bool HasFocus
- {
- set => SetHasFocus (value, this, true);
- get => _hasFocus;
- }
-
- private void SetHasFocus (bool value, View view, bool force = false)
- {
- if (HasFocus != value || force)
- {
- _hasFocus = value;
-
- if (value)
- {
- OnEnter (view);
- }
- else
- {
- OnLeave (view);
- }
-
- SetNeedsDisplay ();
- }
-
- // Remove focus down the chain of subviews if focus is removed
- if (!value && Focused is { })
- {
- View f = Focused;
- f.OnLeave (view);
- f.SetHasFocus (false, view);
- Focused = null;
- }
- }
-
- /// Event fired when the value is being changed.
- public event EventHandler CanFocusChanged;
-
- /// Method invoked when the property from a view is changed.
- public virtual void OnCanFocusChanged () { CanFocusChanged?.Invoke (this, EventArgs.Empty); }
-
- private bool _oldCanFocus;
- private bool _canFocus;
-
- /// Gets or sets a value indicating whether this can focus.
- public bool CanFocus
- {
- get => _canFocus;
- set
- {
- if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value)
- {
- throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
- }
-
- if (_canFocus == value)
- {
- return;
- }
-
- _canFocus = value;
-
- switch (_canFocus)
- {
- case false when _tabIndex > -1:
- TabIndex = -1;
-
- break;
- case true when SuperView?.CanFocus == false && _addingView:
- SuperView.CanFocus = true;
-
- break;
- }
-
- if (_canFocus && _tabIndex == -1)
- {
- TabIndex = SuperView is { } ? SuperView._tabIndexes.IndexOf (this) : -1;
- }
-
- TabStop = _canFocus;
-
- if (!_canFocus && SuperView?.Focused == this)
- {
- SuperView.Focused = null;
- }
-
- if (!_canFocus && HasFocus)
- {
- SetHasFocus (false, this);
- SuperView?.EnsureFocus ();
-
- if (SuperView is { Focused: null })
- {
- SuperView.FocusNext ();
-
- if (SuperView.Focused is null && Application.Current is { })
- {
- Application.Current.FocusNext ();
- }
-
- ApplicationOverlapped.BringOverlappedTopToFront ();
- }
- }
-
- if (_subviews is { } && IsInitialized)
- {
- foreach (View view in _subviews)
- {
- if (view.CanFocus != value)
- {
- if (!value)
- {
- view._oldCanFocus = view.CanFocus;
- view._oldTabIndex = view._tabIndex;
- view.CanFocus = false;
- view._tabIndex = -1;
- }
- else
- {
- if (_addingView)
- {
- view._addingView = true;
- }
-
- view.CanFocus = view._oldCanFocus;
- view._tabIndex = view._oldTabIndex;
- view._addingView = false;
- }
- }
- }
-
- if (this is Toplevel && Application.Current.Focused != this)
- {
- ApplicationOverlapped.BringOverlappedTopToFront ();
- }
- }
-
- OnCanFocusChanged ();
- SetNeedsDisplay ();
- }
- }
-
- ///
- /// Called when a view gets focus.
- ///
- /// The view that is losing focus.
- /// true, if the event was handled, false otherwise.
- public virtual bool OnEnter (View view)
- {
- var args = new FocusEventArgs (view, this);
- Enter?.Invoke (this, args);
-
- if (args.Handled)
- {
- return true;
- }
-
- return false;
- }
-
- /// Method invoked when a view loses focus.
- /// The view that is getting focus.
- /// true, if the event was handled, false otherwise.
- public virtual bool OnLeave (View view)
- {
- var args = new FocusEventArgs (this, view);
- Leave?.Invoke (this, args);
-
- if (args.Handled)
- {
- return true;
- }
-
- return false;
- }
-
- // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
- /// Returns the currently focused Subview inside this view, or null if nothing is focused.
- /// The focused.
- public View Focused { get; private set; }
-
- // BUGBUG: This API is poorly defined and implemented. It does not specify what it means if THIS view is focused and has no subviews.
- /// Returns the most focused Subview in the chain of subviews (the leaf view that has the focus).
- /// The most focused View.
- public View MostFocused
- {
- get
- {
- if (Focused is null)
- {
- return null;
- }
-
- View most = Focused.MostFocused;
-
- if (most is { })
- {
- return most;
- }
-
- return Focused;
- }
- }
-
- /// Causes the specified subview to have focus.
- /// View.
- private void SetFocus (View view)
- {
- if (view is null)
- {
- return;
- }
-
- //Console.WriteLine ($"Request to focus {view}");
- if (!view.CanFocus || !view.Visible || !view.Enabled)
- {
- return;
- }
-
- if (Focused?._hasFocus == true && Focused == view)
- {
- return;
- }
-
- if ((Focused?._hasFocus == true && Focused?.SuperView == view) || view == this)
- {
- if (!view._hasFocus)
- {
- view._hasFocus = true;
- }
-
- return;
- }
-
- // Make sure that this view is a subview
- View c;
-
- for (c = view._superView; c != null; c = c._superView)
- {
- if (c == this)
- {
- break;
- }
- }
-
- if (c is null)
- {
- throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
- }
-
- if (Focused is { })
- {
- Focused.SetHasFocus (false, view);
- }
-
- View f = Focused;
- Focused = view;
- Focused.SetHasFocus (true, f);
- Focused.EnsureFocus ();
-
- // Send focus upwards
- if (SuperView is { })
- {
- SuperView.SetFocus (this);
- }
- else
- {
- SetFocus (this);
- }
- }
-
- /// Causes this view to be focused and entire Superview hierarchy to have the focused order updated.
- public void SetFocus ()
- {
- if (!CanBeVisible (this) || !Enabled)
- {
- if (HasFocus)
- {
- SetHasFocus (false, this);
- }
-
- return;
- }
-
- if (SuperView is { })
- {
- SuperView.SetFocus (this);
- }
- else
- {
- SetFocus (this);
- }
- }
-
- ///
- /// If there is no focused subview, calls or based on .
- /// does nothing.
- ///
- public void EnsureFocus ()
- {
- if (Focused is null && _subviews?.Count > 0)
- {
- if (FocusDirection == NavigationDirection.Forward)
- {
- FocusFirst ();
- }
- else
- {
- FocusLast ();
- }
- }
- }
-
- ///
- /// Focuses the last focusable view in if one exists. If there are no views in then the focus is set to the view itself.
- ///
- public void FocusFirst (bool overlapped = false)
- {
- if (!CanBeVisible (this))
- {
- return;
- }
-
- if (_tabIndexes is null)
- {
- SuperView?.SetFocus (this);
-
- return;
- }
-
- foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)))
- {
- if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
- {
- SetFocus (view);
-
- return;
- }
- }
- }
-
- ///
- /// Focuses the last focusable view in if one exists. If there are no views in then the focus is set to the view itself.
- ///
- public void FocusLast (bool overlapped = false)
- {
- if (!CanBeVisible (this))
- {
- return;
- }
-
- if (_tabIndexes is null)
- {
- SuperView?.SetFocus (this);
-
- return;
- }
-
- foreach (View view in _tabIndexes.Where (v => !overlapped || v.Arrangement.HasFlag (ViewArrangement.Overlapped)).Reverse ())
- {
- if (view.CanFocus && view._tabStop && view.Visible && view.Enabled)
- {
- SetFocus (view);
-
- return;
- }
- }
- }
-
- ///
- /// Focuses the previous view in . If there is no previous view, the focus is set to the view itself.
- ///
- /// if previous was focused, otherwise.
- public bool FocusPrev ()
- {
- if (!CanBeVisible (this))
- {
- return false;
- }
-
- FocusDirection = NavigationDirection.Backward;
-
- if (TabIndexes is null || TabIndexes.Count == 0)
- {
- return false;
- }
-
- if (Focused is null)
- {
- FocusLast ();
-
- return Focused != null;
- }
-
- int focusedIdx = -1;
-
- for (int i = TabIndexes.Count; i > 0;)
- {
- i--;
- View w = TabIndexes [i];
-
- if (w.HasFocus)
- {
- if (w.FocusPrev ())
- {
- return true;
- }
-
- focusedIdx = i;
-
- continue;
- }
-
- if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
- {
- Focused.SetHasFocus (false, w);
-
- // If the focused view is overlapped don't focus on the next if it's not overlapped.
- if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped))
- {
- return false;
- }
-
- // If the focused view is not overlapped and the next is, skip it
- if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
- {
- continue;
- }
-
- if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
- {
- w.FocusLast ();
- }
-
- SetFocus (w);
-
- return true;
- }
- }
-
- // There's no prev view in tab indexes.
- if (Focused is { })
- {
- // Leave Focused
- Focused.SetHasFocus (false, this);
-
- if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
- {
- FocusLast (true);
- return true;
- }
-
- // Signal to caller no next view was found
- Focused = null;
- }
-
- return false;
- }
-
- ///
- /// Focuses the next view in . If there is no next view, the focus is set to the view itself.
- ///
- /// if next was focused, otherwise.
- public bool FocusNext ()
- {
- if (!CanBeVisible (this))
- {
- return false;
- }
-
- FocusDirection = NavigationDirection.Forward;
-
- if (TabIndexes is null || TabIndexes.Count == 0)
- {
- return false;
- }
-
- if (Focused is null)
- {
- FocusFirst ();
-
- return Focused != null;
- }
-
- int focusedIdx = -1;
-
- for (var i = 0; i < TabIndexes.Count; i++)
- {
- View w = TabIndexes [i];
-
- if (w.HasFocus)
- {
- if (w.FocusNext ())
- {
- return true;
- }
-
- focusedIdx = i;
-
- continue;
- }
-
- if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled)
- {
- Focused.SetHasFocus (false, w);
-
- // If the focused view is overlapped don't focus on the next if it's not overlapped.
- if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && !w.Arrangement.HasFlag (ViewArrangement.Overlapped))
- {
- return false;
- }
-
- // If the focused view is not overlapped and the next is, skip it
- if (!Focused.Arrangement.HasFlag (ViewArrangement.Overlapped) && w.Arrangement.HasFlag (ViewArrangement.Overlapped))
- {
- continue;
- }
-
- if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
- {
- w.FocusFirst ();
- }
-
- SetFocus (w);
-
- return true;
- }
- }
-
- // There's no next view in tab indexes.
- if (Focused is { })
- {
- // Leave Focused
- Focused.SetHasFocus (false, this);
-
- if (Focused.Arrangement.HasFlag (ViewArrangement.Overlapped))
- {
- FocusFirst (true);
- return true;
- }
-
- // Signal to caller no next view was found
- Focused = null;
- }
-
- return false;
- }
-
- private View GetMostFocused (View view)
- {
- if (view is null)
- {
- return null;
- }
-
- return view.Focused is { } ? GetMostFocused (view.Focused) : view;
- }
-
- ///
- /// Gets or sets the cursor style to be used when the view is focused. The default is .
- ///
- public CursorVisibility CursorVisibility { get; set; } = CursorVisibility.Invisible;
-
- ///
- /// Positions the cursor in the right position based on the currently focused view in the chain.
- ///
- ///
- ///
- /// Views that are focusable should override to make sure that the cursor is
- /// placed in a location that makes sense. Some terminals do not have a way of hiding the cursor, so it can be
- /// distracting to have the cursor left at the last focused view. So views should make sure that they place the
- /// cursor in a visually sensible place. The default implementation of will place the
- /// cursor at either the hotkey (if defined) or 0,0.
- ///
- ///
- /// Viewport-relative cursor position. Return to ensure the cursor is not visible.
- public virtual Point? PositionCursor ()
- {
- if (IsInitialized && CanFocus && HasFocus)
- {
- // By default, position the cursor at the hotkey (if any) or 0, 0.
- Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
- }
-
- // Returning null will hide the cursor.
- return null;
- }
-
- #endregion Focus
-}
diff --git a/UICatalog/Scenarios/ViewExperiments.cs b/UICatalog/Scenarios/ViewExperiments.cs
index 263a507d9..4c212b8b7 100644
--- a/UICatalog/Scenarios/ViewExperiments.cs
+++ b/UICatalog/Scenarios/ViewExperiments.cs
@@ -27,6 +27,8 @@ public class ViewExperiments : Scenario
Title = "View1",
ColorScheme = Colors.ColorSchemes ["Base"],
Id = "View1",
+ ShadowStyle = ShadowStyle.Transparent,
+ BorderStyle = LineStyle.Double,
CanFocus = true, // Can't drag without this? BUGBUG
Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
};
@@ -46,16 +48,7 @@ public class ViewExperiments : Scenario
//app.Add (view);
- view.Margin.Thickness = new (0);
- view.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
- view.Margin.Data = "Margin";
- view.Border.Thickness = new (1);
- view.Border.LineStyle = LineStyle.Double;
- view.Border.ColorScheme = view.ColorScheme;
- view.Border.Data = "Border";
- view.Padding.Thickness = new (0);
- view.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
- view.Padding.Data = "Padding";
+ view.BorderStyle = LineStyle.Double;
var view2 = new View
{
@@ -66,6 +59,8 @@ public class ViewExperiments : Scenario
Title = "View2",
ColorScheme = Colors.ColorSchemes ["Base"],
Id = "View2",
+ ShadowStyle = ShadowStyle.Transparent,
+ BorderStyle = LineStyle.Double,
CanFocus = true, // Can't drag without this? BUGBUG
Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
};
@@ -85,16 +80,6 @@ public class ViewExperiments : Scenario
view2.Add (button);
view2.Add (button);
- view2.Margin.Thickness = new (0);
- view2.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
- view2.Margin.Data = "Margin";
- view2.Border.Thickness = new (1);
- view2.Border.LineStyle = LineStyle.Double;
- view2.Border.ColorScheme = view2.ColorScheme;
- view2.Border.Data = "Border";
- view2.Padding.Thickness = new (0);
- view2.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
- view2.Padding.Data = "Padding";
button = new ()
{