diff --git a/Terminal.Gui/Application.MainLoopSyncContext.cs b/Terminal.Gui/Application.MainLoopSyncContext.cs
deleted file mode 100644
index 513608e8c..000000000
--- a/Terminal.Gui/Application.MainLoopSyncContext.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-namespace Terminal.Gui;
-
-public static partial class Application
-{
- ///
- /// provides the sync context set while executing code in Terminal.Gui, to let
- /// users use async/await on their code
- ///
- private sealed class MainLoopSyncContext : SynchronizationContext
- {
- public override SynchronizationContext CreateCopy () { return new MainLoopSyncContext (); }
-
- public override void Post (SendOrPostCallback d, object state)
- {
- MainLoop?.AddIdle (
- () =>
- {
- d (state);
-
- return false;
- }
- );
- }
-
- //_mainLoop.Driver.Wakeup ();
- public override void Send (SendOrPostCallback d, object state)
- {
- if (Thread.CurrentThread.ManagedThreadId == _mainThreadId)
- {
- d (state);
- }
- else
- {
- var wasExecuted = false;
-
- Invoke (
- () =>
- {
- d (state);
- wasExecuted = true;
- }
- );
-
- while (!wasExecuted)
- {
- Thread.Sleep (15);
- }
- }
- }
- }
-}
diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application/Application.cs
similarity index 75%
rename from Terminal.Gui/Application.cs
rename to Terminal.Gui/Application/Application.cs
index d825fb872..f015065fe 100644
--- a/Terminal.Gui/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -99,7 +99,6 @@ public static partial class Application
// Don't dispose the Top. It's up to caller dispose it
if (Top is { })
{
-
Debug.Assert (Top.WasDisposed);
// If End wasn't called _cachedRunStateToplevel may be null
@@ -158,6 +157,7 @@ public static partial class Application
KeyDown = null;
KeyUp = null;
SizeChanging = null;
+ ClearKeyBindings ();
Colors.Reset ();
@@ -526,12 +526,8 @@ public static partial class Application
MoveCurrent (Current);
}
- //if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
toplevel.SetRelativeLayout (Driver.Screen.Size);
- //}
-
- // BUGBUG: This call is likely not needed.
toplevel.LayoutSubviews ();
toplevel.PositionToplevels ();
toplevel.FocusFirst ();
@@ -543,6 +539,7 @@ public static partial class Application
toplevel.SetNeedsDisplay ();
toplevel.Draw ();
Driver.UpdateScreen ();
+
if (PositionCursor (toplevel))
{
Driver.UpdateCursor ();
@@ -555,13 +552,14 @@ public static partial class Application
}
///
- /// Calls on the most focused view in the view starting with .
+ /// Calls on the most focused view in the view starting with .
///
///
- /// Does nothing if is or if the most focused view is not visible or enabled.
- ///
- /// If the most focused view is not visible within it's superview, the cursor will be hidden.
- ///
+ /// Does nothing if is or if the most focused view is not visible or
+ /// enabled.
+ ///
+ /// If the most focused view is not visible within it's superview, the cursor will be hidden.
+ ///
///
/// if a view positioned the cursor and the position is visible.
internal static bool PositionCursor (View view)
@@ -585,6 +583,7 @@ public static partial class Application
if (!mostFocused.Visible || !mostFocused.Enabled)
{
Driver.GetCursorVisibility (out CursorVisibility current);
+
if (current != CursorVisibility.Invisible)
{
Driver.SetCursorVisibility (CursorVisibility.Invisible);
@@ -596,6 +595,7 @@ public static partial class Application
// If the view is not visible within it's superview, don't position the cursor
Rectangle mostFocusedViewport = mostFocused.ViewportToScreen (mostFocused.Viewport with { Location = Point.Empty });
Rectangle superViewViewport = mostFocused.SuperView?.ViewportToScreen (mostFocused.SuperView.Viewport with { Location = Point.Empty }) ?? Driver.Screen;
+
if (!superViewViewport.IntersectsWith (mostFocusedViewport))
{
return false;
@@ -677,7 +677,7 @@ public static partial class Application
///
/// The created T object. The caller is responsible for disposing this object.
public static T Run (Func errorHandler = null, ConsoleDriver driver = null)
- where T : Toplevel, new()
+ where T : Toplevel, new ()
{
var top = new T ();
@@ -964,6 +964,7 @@ public static partial class Application
{
state.Toplevel.Draw ();
Driver.UpdateScreen ();
+
//Driver.UpdateCursor ();
}
@@ -1424,516 +1425,4 @@ public static partial class Application
}
#endregion Toplevel handling
-
- #region Mouse handling
-
- /// Disable or enable the mouse. The mouse is enabled by default.
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
- public static bool IsMouseDisabled { get; set; }
-
- /// The current object that wants continuous mouse button pressed events.
- public static View WantContinuousButtonPressedView { get; private set; }
-
- ///
- /// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to
- /// this view until the view calls or the mouse is released.
- ///
- public static View MouseGrabView { get; private set; }
-
- /// Invoked when a view wants to grab the mouse; can be canceled.
- public static event EventHandler GrabbingMouse;
-
- /// Invoked when a view wants un-grab the mouse; can be canceled.
- public static event EventHandler UnGrabbingMouse;
-
- /// Invoked after a view has grabbed the mouse.
- public static event EventHandler GrabbedMouse;
-
- /// Invoked after a view has un-grabbed the mouse.
- public static event EventHandler UnGrabbedMouse;
-
- ///
- /// Grabs the mouse, forcing all mouse events to be routed to the specified view until
- /// is called.
- ///
- /// View that will receive all mouse events until is invoked.
- public static void GrabMouse (View view)
- {
- if (view is null)
- {
- return;
- }
-
- if (!OnGrabbingMouse (view))
- {
- OnGrabbedMouse (view);
- MouseGrabView = view;
- }
- }
-
- /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.
- public static void UngrabMouse ()
- {
- if (MouseGrabView is null)
- {
- return;
- }
-
- if (!OnUnGrabbingMouse (MouseGrabView))
- {
- View view = MouseGrabView;
- MouseGrabView = null;
- OnUnGrabbedMouse (view);
- }
- }
-
- private static bool OnGrabbingMouse (View view)
- {
- if (view is null)
- {
- return false;
- }
-
- var evArgs = new GrabMouseEventArgs (view);
- GrabbingMouse?.Invoke (view, evArgs);
-
- return evArgs.Cancel;
- }
-
- private static bool OnUnGrabbingMouse (View view)
- {
- if (view is null)
- {
- return false;
- }
-
- var evArgs = new GrabMouseEventArgs (view);
- UnGrabbingMouse?.Invoke (view, evArgs);
-
- return evArgs.Cancel;
- }
-
- private static void OnGrabbedMouse (View view)
- {
- if (view is null)
- {
- return;
- }
-
- GrabbedMouse?.Invoke (view, new (view));
- }
-
- private static void OnUnGrabbedMouse (View view)
- {
- if (view is null)
- {
- return;
- }
-
- UnGrabbedMouse?.Invoke (view, new (view));
- }
-
-#nullable enable
-
- // Used by OnMouseEvent to track the last view that was clicked on.
- internal static View? _mouseEnteredView;
-
- /// Event fired when a mouse move or click occurs. Coordinates are screen relative.
- ///
- ///
- /// Use this event to receive mouse events in screen coordinates. Use to
- /// receive mouse events relative to a .
- ///
- /// The will contain the that contains the mouse coordinates.
- ///
- public static event EventHandler? MouseEvent;
-
- /// Called when a mouse event occurs. Raises the event.
- /// This method can be used to simulate a mouse event, e.g. in unit tests.
- /// The mouse event with coordinates relative to the screen.
- internal static void OnMouseEvent (MouseEvent mouseEvent)
- {
- if (IsMouseDisabled)
- {
- return;
- }
-
- var view = View.FindDeepestView (Current, mouseEvent.Position);
-
- if (view is { })
- {
- mouseEvent.View = view;
- }
-
- MouseEvent?.Invoke (null, mouseEvent);
-
- if (mouseEvent.Handled)
- {
- return;
- }
-
- if (MouseGrabView is { })
- {
- // If the mouse is grabbed, send the event to the view that grabbed it.
- // The coordinates are relative to the Bounds of the view that grabbed the mouse.
- Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.Position);
-
- var viewRelativeMouseEvent = new MouseEvent
- {
- Position = frameLoc,
- Flags = mouseEvent.Flags,
- ScreenPosition = mouseEvent.Position,
- View = MouseGrabView
- };
-
- if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false)
- {
- // The mouse has moved outside the bounds of the view that grabbed the mouse
- _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent);
- }
-
- //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
- if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true)
- {
- return;
- }
- }
-
- if (view is { WantContinuousButtonPressed: true })
- {
- WantContinuousButtonPressedView = view;
- }
- else
- {
- WantContinuousButtonPressedView = null;
- }
-
-
- if (view is not Adornment)
- {
- if ((view is null || view == OverlappedTop)
- && Current is { Modal: false }
- && OverlappedTop != null
- && mouseEvent.Flags != MouseFlags.ReportMousePosition
- && mouseEvent.Flags != 0)
- {
- // This occurs when there are multiple overlapped "tops"
- // E.g. "Mdi" - in the Background Worker Scenario
- View? top = FindDeepestTop (Top, mouseEvent.Position);
- view = View.FindDeepestView (top, mouseEvent.Position);
-
- if (view is { } && view != OverlappedTop && top != Current && top is { })
- {
- MoveCurrent ((Toplevel)top);
- }
- }
- }
-
- if (view is null)
- {
- return;
- }
-
- MouseEvent? me = null;
-
- if (view is Adornment adornment)
- {
- Point frameLoc = adornment.ScreenToFrame (mouseEvent.Position);
-
- me = new ()
- {
- Position = frameLoc,
- Flags = mouseEvent.Flags,
- ScreenPosition = mouseEvent.Position,
- View = view
- };
- }
- else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position))
- {
- Point viewportLocation = view.ScreenToViewport (mouseEvent.Position);
-
- me = new ()
- {
- Position = viewportLocation,
- Flags = mouseEvent.Flags,
- ScreenPosition = mouseEvent.Position,
- View = view
- };
- }
-
- if (me is null)
- {
- return;
- }
-
- if (_mouseEnteredView is null)
- {
- _mouseEnteredView = view;
- view.NewMouseEnterEvent (me);
- }
- else if (_mouseEnteredView != view)
- {
- _mouseEnteredView.NewMouseLeaveEvent (me);
- view.NewMouseEnterEvent (me);
- _mouseEnteredView = view;
- }
-
- if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
- {
- return;
- }
-
- WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
-
- //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
-
- while (view.NewMouseEvent (me) != true)
- {
- if (MouseGrabView is { })
- {
- break;
- }
-
- if (view is Adornment adornmentView)
- {
- view = adornmentView.Parent.SuperView;
- }
- else
- {
- view = view.SuperView;
- }
-
- if (view is null)
- {
- break;
- }
-
- Point boundsPoint = view.ScreenToViewport (mouseEvent.Position);
-
- me = new ()
- {
- Position = boundsPoint,
- Flags = mouseEvent.Flags,
- ScreenPosition = mouseEvent.Position,
- View = view
- };
- }
-
- BringOverlappedTopToFront ();
- }
-#nullable restore
-
- #endregion Mouse handling
-
- #region Keyboard handling
-
- private static Key _alternateForwardKey = Key.Empty; // Defined in config.json
-
- /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
- [JsonConverter (typeof (KeyJsonConverter))]
- public static Key AlternateForwardKey
- {
- get => _alternateForwardKey;
- set
- {
- if (_alternateForwardKey != value)
- {
- Key oldKey = _alternateForwardKey;
- _alternateForwardKey = value;
- OnAlternateForwardKeyChanged (new (oldKey, value));
- }
- }
- }
-
- private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
- {
- foreach (Toplevel top in _topLevels.ToArray ())
- {
- top.OnAlternateForwardKeyChanged (e);
- }
- }
-
- private static Key _alternateBackwardKey = Key.Empty; // Defined in config.json
-
- /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
- [JsonConverter (typeof (KeyJsonConverter))]
- public static Key AlternateBackwardKey
- {
- get => _alternateBackwardKey;
- set
- {
- if (_alternateBackwardKey != value)
- {
- Key oldKey = _alternateBackwardKey;
- _alternateBackwardKey = value;
- OnAlternateBackwardKeyChanged (new (oldKey, value));
- }
- }
- }
-
- private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey)
- {
- foreach (Toplevel top in _topLevels.ToArray ())
- {
- top.OnAlternateBackwardKeyChanged (oldKey);
- }
- }
-
- private static Key _quitKey = Key.Empty; // Defined in config.json
-
- /// Gets or sets the key to quit the application.
- [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
- [JsonConverter (typeof (KeyJsonConverter))]
- public static Key QuitKey
- {
- get => _quitKey;
- set
- {
- if (_quitKey != value)
- {
- Key oldKey = _quitKey;
- _quitKey = value;
- OnQuitKeyChanged (new (oldKey, value));
- }
- }
- }
-
- private static void OnQuitKeyChanged (KeyChangedEventArgs e)
- {
- // Duplicate the list so if it changes during enumeration we're safe
- foreach (Toplevel top in _topLevels.ToArray ())
- {
- top.OnQuitKeyChanged (e);
- }
- }
-
- ///
- /// Event fired when the user presses a key. Fired by .
- ///
- /// Set to to indicate the key was handled and to prevent
- /// additional processing.
- ///
- ///
- ///
- /// All drivers support firing the event. Some drivers (Curses) do not support firing the
- /// and events.
- /// Fired after and before .
- ///
- public static event EventHandler KeyDown;
-
- ///
- /// Called by the when the user presses a key. Fires the event
- /// then calls on all top level views. Called after and
- /// before .
- ///
- /// Can be used to simulate key press events.
- ///
- /// if the key was handled.
- public static bool OnKeyDown (Key keyEvent)
- {
- if (!_initialized)
- {
- return true;
- }
-
- KeyDown?.Invoke (null, keyEvent);
-
- if (keyEvent.Handled)
- {
- return true;
- }
-
- foreach (Toplevel topLevel in _topLevels.ToList ())
- {
- if (topLevel.NewKeyDownEvent (keyEvent))
- {
- return true;
- }
-
- if (topLevel.Modal)
- {
- break;
- }
- }
-
- // Invoke any Global KeyBindings
- foreach (Toplevel topLevel in _topLevels.ToList ())
- {
- foreach (View view in topLevel.Subviews.Where (
- v => v.KeyBindings.TryGet (
- keyEvent,
- KeyBindingScope.Application,
- out KeyBinding _
- )
- ))
- {
- if (view.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.Application, out KeyBinding _))
- {
- bool? handled = view.OnInvokingKeyBindings (keyEvent);
-
- if (handled is { } && (bool)handled)
- {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- ///
- /// Event fired when the user releases a key. Fired by .
- ///
- /// Set to to indicate the key was handled and to prevent
- /// additional processing.
- ///
- ///
- ///
- /// All drivers support firing the event. Some drivers (Curses) do not support firing the
- /// and events.
- /// Fired after .
- ///
- public static event EventHandler KeyUp;
-
- ///
- /// Called by the when the user releases a key. Fires the event
- /// then calls on all top level views. Called after .
- ///
- /// Can be used to simulate key press events.
- ///
- /// if the key was handled.
- public static bool OnKeyUp (Key a)
- {
- if (!_initialized)
- {
- return true;
- }
-
- KeyUp?.Invoke (null, a);
-
- if (a.Handled)
- {
- return true;
- }
-
- foreach (Toplevel topLevel in _topLevels.ToList ())
- {
- if (topLevel.NewKeyUpEvent (a))
- {
- return true;
- }
-
- if (topLevel.Modal)
- {
- break;
- }
- }
-
- return false;
- }
-
- #endregion Keyboard handling
}
diff --git a/Terminal.Gui/Application/ApplicationKeyboard.cs b/Terminal.Gui/Application/ApplicationKeyboard.cs
new file mode 100644
index 000000000..0a56ce712
--- /dev/null
+++ b/Terminal.Gui/Application/ApplicationKeyboard.cs
@@ -0,0 +1,298 @@
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+
+partial class Application
+{
+ private static Key _alternateForwardKey = Key.Empty; // Defined in config.json
+
+ /// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ [JsonConverter (typeof (KeyJsonConverter))]
+ public static Key AlternateForwardKey
+ {
+ get => _alternateForwardKey;
+ set
+ {
+ if (_alternateForwardKey != value)
+ {
+ Key oldKey = _alternateForwardKey;
+ _alternateForwardKey = value;
+ OnAlternateForwardKeyChanged (new (oldKey, value));
+ }
+ }
+ }
+
+ private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
+ {
+ foreach (Toplevel top in _topLevels.ToArray ())
+ {
+ top.OnAlternateForwardKeyChanged (e);
+ }
+ }
+
+ private static Key _alternateBackwardKey = Key.Empty; // Defined in config.json
+
+ /// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ [JsonConverter (typeof (KeyJsonConverter))]
+ public static Key AlternateBackwardKey
+ {
+ get => _alternateBackwardKey;
+ set
+ {
+ if (_alternateBackwardKey != value)
+ {
+ Key oldKey = _alternateBackwardKey;
+ _alternateBackwardKey = value;
+ OnAlternateBackwardKeyChanged (new (oldKey, value));
+ }
+ }
+ }
+
+ private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey)
+ {
+ foreach (Toplevel top in _topLevels.ToArray ())
+ {
+ top.OnAlternateBackwardKeyChanged (oldKey);
+ }
+ }
+
+ private static Key _quitKey = Key.Empty; // Defined in config.json
+
+ /// Gets or sets the key to quit the application.
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ [JsonConverter (typeof (KeyJsonConverter))]
+ public static Key QuitKey
+ {
+ get => _quitKey;
+ set
+ {
+ if (_quitKey != value)
+ {
+ Key oldKey = _quitKey;
+ _quitKey = value;
+ OnQuitKeyChanged (new (oldKey, value));
+ }
+ }
+ }
+
+ private static void OnQuitKeyChanged (KeyChangedEventArgs e)
+ {
+ // Duplicate the list so if it changes during enumeration we're safe
+ foreach (Toplevel top in _topLevels.ToArray ())
+ {
+ top.OnQuitKeyChanged (e);
+ }
+ }
+
+ ///
+ /// Event fired when the user presses a key. Fired by .
+ ///
+ /// Set to to indicate the key was handled and to prevent
+ /// additional processing.
+ ///
+ ///
+ ///
+ /// All drivers support firing the event. Some drivers (Curses) do not support firing the
+ /// and events.
+ /// Fired after and before .
+ ///
+ public static event EventHandler KeyDown;
+
+ ///
+ /// Called by the when the user presses a key. Fires the event
+ /// then calls on all top level views. Called after and
+ /// before .
+ ///
+ /// Can be used to simulate key press events.
+ ///
+ /// if the key was handled.
+ public static bool OnKeyDown (Key keyEvent)
+ {
+ if (!_initialized)
+ {
+ return true;
+ }
+
+ KeyDown?.Invoke (null, keyEvent);
+
+ if (keyEvent.Handled)
+ {
+ return true;
+ }
+
+ foreach (Toplevel topLevel in _topLevels.ToList ())
+ {
+ if (topLevel.NewKeyDownEvent (keyEvent))
+ {
+ return true;
+ }
+
+ if (topLevel.Modal)
+ {
+ break;
+ }
+ }
+
+ // Invoke any global (Application-scoped) KeyBindings.
+ // The first view that handles the key will stop the loop.
+ foreach (KeyValuePair> binding in _keyBindings.Where (b => b.Key == keyEvent.KeyCode))
+ {
+ foreach (View view in binding.Value)
+ {
+ bool? handled = view?.OnInvokingKeyBindings (keyEvent);
+
+ if (handled != null && (bool)handled)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Event fired when the user releases a key. Fired by .
+ ///
+ /// Set to to indicate the key was handled and to prevent
+ /// additional processing.
+ ///
+ ///
+ ///
+ /// All drivers support firing the event. Some drivers (Curses) do not support firing the
+ /// and events.
+ /// Fired after .
+ ///
+ public static event EventHandler KeyUp;
+
+ ///
+ /// Called by the when the user releases a key. Fires the event
+ /// then calls on all top level views. Called after .
+ ///
+ /// Can be used to simulate key press events.
+ ///
+ /// if the key was handled.
+ public static bool OnKeyUp (Key a)
+ {
+ if (!_initialized)
+ {
+ return true;
+ }
+
+ KeyUp?.Invoke (null, a);
+
+ if (a.Handled)
+ {
+ return true;
+ }
+
+ foreach (Toplevel topLevel in _topLevels.ToList ())
+ {
+ if (topLevel.NewKeyUpEvent (a))
+ {
+ return true;
+ }
+
+ if (topLevel.Modal)
+ {
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 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.
+ internal static void AddKeyBinding (Key key, View view)
+ {
+ if (!_keyBindings.ContainsKey (key))
+ {
+ _keyBindings [key] = [];
+ }
+
+ _keyBindings [key].Add (view);
+ }
+
+ ///
+ /// Gets the list of Views that have key bindings.
+ ///
+ ///
+ /// This is an internal method used by the class to add Application key bindings.
+ ///
+ /// The list of Views that have Application-scoped key bindings.
+ internal static List GetViewsWithKeyBindings () { return _keyBindings.Values.SelectMany (v => v).ToList (); }
+
+ ///
+ /// Gets the list of Views that have key bindings for the specified key.
+ ///
+ ///
+ /// This is an internal method used by the class to add Application key bindings.
+ ///
+ /// The key to check.
+ /// Outputs the list of views bound to
+ /// if successful.
+ internal static bool TryGetKeyBindings (Key key, out List views) { return _keyBindings.TryGetValue (key, out views); }
+
+ ///
+ /// Removes an scoped key binding.
+ ///
+ ///
+ /// This is an internal method used by the class to remove Application key bindings.
+ ///
+ /// The key that was bound.
+ /// The view that is bound to the key.
+ internal static void RemoveKeyBinding (Key key, View view)
+ {
+ if (_keyBindings.TryGetValue (key, out List views))
+ {
+ views.Remove (view);
+
+ if (views.Count == 0)
+ {
+ _keyBindings.Remove (key);
+ }
+ }
+ }
+
+ ///
+ /// Removes all scoped key bindings for the specified view.
+ ///
+ ///
+ /// This is an internal method used by the class to remove Application key bindings.
+ ///
+ /// The view that is bound to the key.
+ internal static void ClearKeyBindings (View view)
+ {
+ foreach (Key key in _keyBindings.Keys)
+ {
+ _keyBindings [key].Remove (view);
+ }
+ }
+
+ ///
+ /// Removes all scoped key bindings for the specified view.
+ ///
+ ///
+ /// This is an internal method used by the class to remove Application key bindings.
+ ///
+ internal static void ClearKeyBindings () { _keyBindings.Clear (); }
+}
diff --git a/Terminal.Gui/Application/ApplicationMouse.cs b/Terminal.Gui/Application/ApplicationMouse.cs
new file mode 100644
index 000000000..9f2a95339
--- /dev/null
+++ b/Terminal.Gui/Application/ApplicationMouse.cs
@@ -0,0 +1,302 @@
+namespace Terminal.Gui;
+
+partial class Application
+{
+ #region Mouse handling
+
+ /// Disable or enable the mouse. The mouse is enabled by default.
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ public static bool IsMouseDisabled { get; set; }
+
+ /// The current object that wants continuous mouse button pressed events.
+ public static View WantContinuousButtonPressedView { get; private set; }
+
+ ///
+ /// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to
+ /// this view until the view calls or the mouse is released.
+ ///
+ public static View MouseGrabView { get; private set; }
+
+ /// Invoked when a view wants to grab the mouse; can be canceled.
+ public static event EventHandler GrabbingMouse;
+
+ /// Invoked when a view wants un-grab the mouse; can be canceled.
+ public static event EventHandler UnGrabbingMouse;
+
+ /// Invoked after a view has grabbed the mouse.
+ public static event EventHandler GrabbedMouse;
+
+ /// Invoked after a view has un-grabbed the mouse.
+ public static event EventHandler UnGrabbedMouse;
+
+ ///
+ /// Grabs the mouse, forcing all mouse events to be routed to the specified view until
+ /// is called.
+ ///
+ /// View that will receive all mouse events until is invoked.
+ public static void GrabMouse (View view)
+ {
+ if (view is null)
+ {
+ return;
+ }
+
+ if (!OnGrabbingMouse (view))
+ {
+ OnGrabbedMouse (view);
+ MouseGrabView = view;
+ }
+ }
+
+ /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.
+ public static void UngrabMouse ()
+ {
+ if (MouseGrabView is null)
+ {
+ return;
+ }
+
+ if (!OnUnGrabbingMouse (MouseGrabView))
+ {
+ View view = MouseGrabView;
+ MouseGrabView = null;
+ OnUnGrabbedMouse (view);
+ }
+ }
+
+ private static bool OnGrabbingMouse (View view)
+ {
+ if (view is null)
+ {
+ return false;
+ }
+
+ var evArgs = new GrabMouseEventArgs (view);
+ GrabbingMouse?.Invoke (view, evArgs);
+
+ return evArgs.Cancel;
+ }
+
+ private static bool OnUnGrabbingMouse (View view)
+ {
+ if (view is null)
+ {
+ return false;
+ }
+
+ var evArgs = new GrabMouseEventArgs (view);
+ UnGrabbingMouse?.Invoke (view, evArgs);
+
+ return evArgs.Cancel;
+ }
+
+ private static void OnGrabbedMouse (View view)
+ {
+ if (view is null)
+ {
+ return;
+ }
+
+ GrabbedMouse?.Invoke (view, new (view));
+ }
+
+ private static void OnUnGrabbedMouse (View view)
+ {
+ if (view is null)
+ {
+ return;
+ }
+
+ UnGrabbedMouse?.Invoke (view, new (view));
+ }
+
+#nullable enable
+
+ // Used by OnMouseEvent to track the last view that was clicked on.
+ internal static View? _mouseEnteredView;
+
+ /// Event fired when a mouse move or click occurs. Coordinates are screen relative.
+ ///
+ ///
+ /// Use this event to receive mouse events in screen coordinates. Use to
+ /// receive mouse events relative to a .
+ ///
+ /// The will contain the that contains the mouse coordinates.
+ ///
+ public static event EventHandler? MouseEvent;
+
+ /// Called when a mouse event occurs. Raises the event.
+ /// This method can be used to simulate a mouse event, e.g. in unit tests.
+ /// The mouse event with coordinates relative to the screen.
+ internal static void OnMouseEvent (MouseEvent mouseEvent)
+ {
+ if (IsMouseDisabled)
+ {
+ return;
+ }
+
+ var view = View.FindDeepestView (Current, mouseEvent.Position);
+
+ if (view is { })
+ {
+ mouseEvent.View = view;
+ }
+
+ MouseEvent?.Invoke (null, mouseEvent);
+
+ if (mouseEvent.Handled)
+ {
+ return;
+ }
+
+ if (MouseGrabView is { })
+ {
+ // If the mouse is grabbed, send the event to the view that grabbed it.
+ // The coordinates are relative to the Bounds of the view that grabbed the mouse.
+ Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.Position);
+
+ var viewRelativeMouseEvent = new MouseEvent
+ {
+ Position = frameLoc,
+ Flags = mouseEvent.Flags,
+ ScreenPosition = mouseEvent.Position,
+ View = MouseGrabView
+ };
+
+ if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false)
+ {
+ // The mouse has moved outside the bounds of the view that grabbed the mouse
+ _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent);
+ }
+
+ //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
+ if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true)
+ {
+ return;
+ }
+ }
+
+ if (view is { WantContinuousButtonPressed: true })
+ {
+ WantContinuousButtonPressedView = view;
+ }
+ else
+ {
+ WantContinuousButtonPressedView = null;
+ }
+
+ if (view is not Adornment)
+ {
+ if ((view is null || view == OverlappedTop)
+ && Current is { Modal: false }
+ && OverlappedTop != null
+ && mouseEvent.Flags != MouseFlags.ReportMousePosition
+ && mouseEvent.Flags != 0)
+ {
+ // This occurs when there are multiple overlapped "tops"
+ // E.g. "Mdi" - in the Background Worker Scenario
+ View? top = FindDeepestTop (Top, mouseEvent.Position);
+ view = View.FindDeepestView (top, mouseEvent.Position);
+
+ if (view is { } && view != OverlappedTop && top != Current && top is { })
+ {
+ MoveCurrent ((Toplevel)top);
+ }
+ }
+ }
+
+ if (view is null)
+ {
+ return;
+ }
+
+ MouseEvent? me = null;
+
+ if (view is Adornment adornment)
+ {
+ Point frameLoc = adornment.ScreenToFrame (mouseEvent.Position);
+
+ me = new ()
+ {
+ Position = frameLoc,
+ Flags = mouseEvent.Flags,
+ ScreenPosition = mouseEvent.Position,
+ View = view
+ };
+ }
+ else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position))
+ {
+ Point viewportLocation = view.ScreenToViewport (mouseEvent.Position);
+
+ me = new ()
+ {
+ Position = viewportLocation,
+ Flags = mouseEvent.Flags,
+ ScreenPosition = mouseEvent.Position,
+ View = view
+ };
+ }
+
+ if (me is null)
+ {
+ return;
+ }
+
+ if (_mouseEnteredView is null)
+ {
+ _mouseEnteredView = view;
+ view.NewMouseEnterEvent (me);
+ }
+ else if (_mouseEnteredView != view)
+ {
+ _mouseEnteredView.NewMouseLeaveEvent (me);
+ view.NewMouseEnterEvent (me);
+ _mouseEnteredView = view;
+ }
+
+ if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
+ {
+ return;
+ }
+
+ WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
+
+ //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
+
+ while (view.NewMouseEvent (me) != true)
+ {
+ if (MouseGrabView is { })
+ {
+ break;
+ }
+
+ if (view is Adornment adornmentView)
+ {
+ view = adornmentView.Parent.SuperView;
+ }
+ else
+ {
+ view = view.SuperView;
+ }
+
+ if (view is null)
+ {
+ break;
+ }
+
+ Point boundsPoint = view.ScreenToViewport (mouseEvent.Position);
+
+ me = new ()
+ {
+ Position = boundsPoint,
+ Flags = mouseEvent.Flags,
+ ScreenPosition = mouseEvent.Position,
+ View = view
+ };
+ }
+
+ BringOverlappedTopToFront ();
+ }
+
+ #endregion Mouse handling
+}
diff --git a/Terminal.Gui/IterationEventArgs.cs b/Terminal.Gui/Application/IterationEventArgs.cs
similarity index 100%
rename from Terminal.Gui/IterationEventArgs.cs
rename to Terminal.Gui/Application/IterationEventArgs.cs
diff --git a/Terminal.Gui/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs
similarity index 100%
rename from Terminal.Gui/MainLoop.cs
rename to Terminal.Gui/Application/MainLoop.cs
diff --git a/Terminal.Gui/Application/MainLoopSyncContext.cs b/Terminal.Gui/Application/MainLoopSyncContext.cs
new file mode 100644
index 000000000..5290a2076
--- /dev/null
+++ b/Terminal.Gui/Application/MainLoopSyncContext.cs
@@ -0,0 +1,48 @@
+namespace Terminal.Gui;
+
+///
+/// provides the sync context set while executing code in Terminal.Gui, to let
+/// users use async/await on their code
+///
+internal sealed class MainLoopSyncContext : SynchronizationContext
+{
+ public override SynchronizationContext CreateCopy () { return new MainLoopSyncContext (); }
+
+ public override void Post (SendOrPostCallback d, object state)
+ {
+ Application.MainLoop?.AddIdle (
+ () =>
+ {
+ d (state);
+
+ return false;
+ }
+ );
+ }
+
+ //_mainLoop.Driver.Wakeup ();
+ public override void Send (SendOrPostCallback d, object state)
+ {
+ if (Thread.CurrentThread.ManagedThreadId == Application._mainThreadId)
+ {
+ d (state);
+ }
+ else
+ {
+ var wasExecuted = false;
+
+ Application.Invoke (
+ () =>
+ {
+ d (state);
+ wasExecuted = true;
+ }
+ );
+
+ while (!wasExecuted)
+ {
+ Thread.Sleep (15);
+ }
+ }
+ }
+}
diff --git a/Terminal.Gui/RunState.cs b/Terminal.Gui/Application/RunState.cs
similarity index 100%
rename from Terminal.Gui/RunState.cs
rename to Terminal.Gui/Application/RunState.cs
diff --git a/Terminal.Gui/RunStateEventArgs.cs b/Terminal.Gui/Application/RunStateEventArgs.cs
similarity index 100%
rename from Terminal.Gui/RunStateEventArgs.cs
rename to Terminal.Gui/Application/RunStateEventArgs.cs
diff --git a/Terminal.Gui/Timeout.cs b/Terminal.Gui/Application/Timeout.cs
similarity index 100%
rename from Terminal.Gui/Timeout.cs
rename to Terminal.Gui/Application/Timeout.cs
diff --git a/Terminal.Gui/TimeoutEventArgs.cs b/Terminal.Gui/Application/TimeoutEventArgs.cs
similarity index 100%
rename from Terminal.Gui/TimeoutEventArgs.cs
rename to Terminal.Gui/Application/TimeoutEventArgs.cs
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
index 7187d7191..b5ab6fa9c 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
@@ -315,12 +315,31 @@ public abstract class ConsoleDriver
{
Contents [row, c] = new Cell
{
- Rune = (Rune)' ',
- Attribute = new Attribute (Color.White, Color.Black),
+ Rune = (Rune)' ',
+ Attribute = new Attribute (Color.White, Color.Black),
IsDirty = true
};
- _dirtyLines [row] = true;
}
+ _dirtyLines [row] = true;
+ }
+ }
+ }
+
+ ///
+ /// Sets as dirty for situations where views
+ /// don't need layout and redrawing, but just refresh the screen.
+ ///
+ public void SetContentsAsDirty ()
+ {
+ lock (Contents)
+ {
+ for (var row = 0; row < Rows; row++)
+ {
+ for (var c = 0; c < Cols; c++)
+ {
+ Contents [row, c].IsDirty = true;
+ }
+ _dirtyLines [row] = true;
}
}
}
diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs
index 2d5e28acf..b62ef17a4 100644
--- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs
+++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs
@@ -76,7 +76,7 @@ public class EscSeqRequests
return false;
}
- if (found is { } && found.NumOutstanding > 0)
+ if (found is { NumOutstanding: > 0 })
{
return true;
}
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
index f7f6df8b0..0dd0e9038 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
@@ -208,7 +208,7 @@ internal class NetEvents : IDisposable
while (!cancellationToken.IsCancellationRequested)
{
- Task.Delay (100);
+ Task.Delay (100, cancellationToken).Wait (cancellationToken);
if (Console.KeyAvailable)
{
@@ -223,7 +223,7 @@ internal class NetEvents : IDisposable
private void ProcessInputQueue ()
{
- while (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
+ while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
{
try
{
@@ -242,13 +242,8 @@ internal class NetEvents : IDisposable
ConsoleModifiers mod = 0;
ConsoleKeyInfo newConsoleKeyInfo = default;
- while (true)
+ while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
{
- if (_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
- {
- return;
- }
-
ConsoleKeyInfo consoleKeyInfo;
try
@@ -338,7 +333,7 @@ internal class NetEvents : IDisposable
while (!cancellationToken.IsCancellationRequested)
{
// Wait for a while then check if screen has changed sizes
- Task.Delay (500, cancellationToken);
+ Task.Delay (500, cancellationToken).Wait (cancellationToken);
int buffHeight, buffWidth;
@@ -367,13 +362,8 @@ internal class NetEvents : IDisposable
cancellationToken.ThrowIfCancellationRequested ();
}
- while (true)
+ while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
{
- if (_inputReadyCancellationTokenSource.IsCancellationRequested)
- {
- return;
- }
-
try
{
_winChange.Wait (_inputReadyCancellationTokenSource.Token);
@@ -852,11 +842,37 @@ internal class NetDriver : ConsoleDriver
{ }
}
- #region Not Implemented
+ public override void Suspend ()
+ {
+ if (Environment.OSVersion.Platform != PlatformID.Unix)
+ {
+ return;
+ }
- public override void Suspend () { throw new NotImplementedException (); }
+ StopReportingMouseMoves ();
- #endregion
+ if (!RunningUnitTests)
+ {
+ Console.ResetColor ();
+ Console.Clear ();
+
+ //Disable alternative screen buffer.
+ Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+ //Set cursor key to cursor.
+ Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+
+ Platform.Suspend ();
+
+ //Enable alternative screen buffer.
+ Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+ SetContentsAsDirty ();
+ Refresh ();
+ }
+
+ StartReportingMouseMoves ();
+ }
public override void UpdateScreen ()
{
@@ -877,7 +893,7 @@ internal class NetDriver : ConsoleDriver
Attribute? redrawAttr = null;
int lastCol = -1;
- CursorVisibility? savedVisibitity = _cachedCursorVisibility;
+ CursorVisibility? savedVisibility = _cachedCursorVisibility;
SetCursorVisibility (CursorVisibility.Invisible);
for (int row = top; row < rows; row++)
@@ -1006,7 +1022,7 @@ internal class NetDriver : ConsoleDriver
SetCursorPosition (0, 0);
- _cachedCursorVisibility = savedVisibitity;
+ _cachedCursorVisibility = savedVisibility;
void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
{
@@ -1333,12 +1349,9 @@ internal class NetDriver : ConsoleDriver
{
_cachedCursorVisibility = visibility;
- bool isVisible = RunningUnitTests
- ? visibility == CursorVisibility.Default
- : Console.CursorVisible = visibility == CursorVisibility.Default;
- Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+ Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
- return isVisible;
+ return visibility == CursorVisibility.Default;
}
public override bool EnsureCursorVisibility ()
@@ -1667,7 +1680,7 @@ internal class NetMainLoop : IMainLoopDriver
private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
private readonly Queue _resultQueue = new ();
private readonly ManualResetEventSlim _waitForProbe = new (false);
- private CancellationTokenSource _eventReadyTokenSource = new ();
+ private readonly CancellationTokenSource _eventReadyTokenSource = new ();
private MainLoop _mainLoop;
/// Initializes the class with the console driver.
@@ -1719,14 +1732,13 @@ internal class NetMainLoop : IMainLoopDriver
_eventReady.Reset ();
}
+ _eventReadyTokenSource.Token.ThrowIfCancellationRequested ();
+
if (!_eventReadyTokenSource.IsCancellationRequested)
{
return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
}
- _eventReadyTokenSource.Dispose ();
- _eventReadyTokenSource = new CancellationTokenSource ();
-
return true;
}
@@ -1783,26 +1795,21 @@ internal class NetMainLoop : IMainLoopDriver
return;
}
+ _inputHandlerTokenSource.Token.ThrowIfCancellationRequested ();
+
if (_resultQueue.Count == 0)
{
_resultQueue.Enqueue (_netEvents.DequeueInput ());
}
- try
+ while (_resultQueue.Count > 0 && _resultQueue.Peek () is null)
{
- while (_resultQueue.Peek () is null)
- {
- _resultQueue.Dequeue ();
- }
-
- if (_resultQueue.Count > 0)
- {
- _eventReady.Set ();
- }
+ _resultQueue.Dequeue ();
}
- catch (InvalidOperationException)
+
+ if (_resultQueue.Count > 0)
{
- // Ignore
+ _eventReady.Set ();
}
}
}
diff --git a/Terminal.Gui/Drawing/Aligner.cs b/Terminal.Gui/Drawing/Aligner.cs
new file mode 100644
index 000000000..1c96a2ac0
--- /dev/null
+++ b/Terminal.Gui/Drawing/Aligner.cs
@@ -0,0 +1,369 @@
+using System.ComponentModel;
+
+namespace Terminal.Gui;
+
+///
+/// Aligns items within a container based on the specified . Both horizontal and vertical
+/// alignments are supported.
+///
+public class Aligner : INotifyPropertyChanged
+{
+ private Alignment _alignment;
+
+ ///
+ /// Gets or sets how the aligns items within a container.
+ ///
+ ///
+ ///
+ /// provides additional options for aligning items in a container.
+ ///
+ ///
+ public Alignment Alignment
+ {
+ get => _alignment;
+ set
+ {
+ _alignment = value;
+ PropertyChanged?.Invoke (this, new (nameof (Alignment)));
+ }
+ }
+
+ private AlignmentModes _alignmentMode = AlignmentModes.StartToEnd;
+
+ ///
+ /// Gets or sets the modes controlling .
+ ///
+ public AlignmentModes AlignmentModes
+ {
+ get => _alignmentMode;
+ set
+ {
+ _alignmentMode = value;
+ PropertyChanged?.Invoke (this, new (nameof (AlignmentModes)));
+ }
+ }
+
+ private int _containerSize;
+
+ ///
+ /// The size of the container.
+ ///
+ public int ContainerSize
+ {
+ get => _containerSize;
+ set
+ {
+ _containerSize = value;
+ PropertyChanged?.Invoke (this, new (nameof (ContainerSize)));
+ }
+ }
+
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Takes a list of item sizes and returns a list of the positions of those items when aligned within
+ ///
+ /// using the and settings.
+ ///
+ /// The sizes of the items to align.
+ /// The locations of the items, from left/top to right/bottom.
+ public int [] Align (int [] sizes) { return Align (Alignment, AlignmentModes, ContainerSize, sizes); }
+
+ ///
+ /// Takes a list of item sizes and returns a list of the positions of those items when aligned within
+ ///
+ /// using specified parameters.
+ ///
+ /// Specifies how the items will be aligned.
+ ///
+ /// The size of the container.
+ /// The sizes of the items to align.
+ /// The positions of the items, from left/top to right/bottom.
+ public static int [] Align (in Alignment alignment, in AlignmentModes alignmentMode, in int containerSize, in int [] sizes)
+ {
+ if (sizes.Length == 0)
+ {
+ return [];
+ }
+
+ var sizesCopy = sizes;
+ if (alignmentMode.FastHasFlags (AlignmentModes.EndToStart))
+ {
+ sizesCopy = sizes.Reverse ().ToArray ();
+ }
+
+ int maxSpaceBetweenItems = alignmentMode.FastHasFlags (AlignmentModes.AddSpaceBetweenItems) ? 1 : 0;
+ int totalItemsSize = sizes.Sum ();
+ int totalGaps = sizes.Length - 1; // total gaps between items
+ int totalItemsAndSpaces = totalItemsSize + totalGaps * maxSpaceBetweenItems; // total size of items and spacesToGive if we had enough room
+ int spacesToGive = totalGaps * maxSpaceBetweenItems; // We'll decrement this below to place one space between each item until we run out
+
+ if (totalItemsSize >= containerSize)
+ {
+ spacesToGive = 0;
+ }
+ else if (totalItemsAndSpaces > containerSize)
+ {
+ spacesToGive = containerSize - totalItemsSize;
+ }
+
+ switch (alignment)
+ {
+ case Alignment.Start:
+ switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems)
+ {
+ case AlignmentModes.StartToEnd:
+ return Start (in sizesCopy, maxSpaceBetweenItems, spacesToGive);
+
+ case AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast:
+ return IgnoreLast (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive);
+
+ case AlignmentModes.EndToStart:
+ return End (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive).Reverse ().ToArray ();
+
+ case AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast:
+ return IgnoreFirst (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive).Reverse ().ToArray (); ;
+ }
+
+ break;
+
+ case Alignment.End:
+ switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems)
+ {
+ case AlignmentModes.StartToEnd:
+ return End (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive);
+
+ case AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast:
+ return IgnoreFirst (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive);
+
+ case AlignmentModes.EndToStart:
+ return Start (in sizesCopy, maxSpaceBetweenItems, spacesToGive).Reverse ().ToArray ();
+
+ case AlignmentModes.EndToStart | AlignmentModes.IgnoreFirstOrLast:
+ return IgnoreLast (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive).Reverse ().ToArray (); ;
+ }
+
+ break;
+
+ case Alignment.Center:
+ switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems)
+ {
+ case AlignmentModes.StartToEnd:
+ return Center (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive);
+
+ case AlignmentModes.EndToStart:
+ return Center (in sizesCopy, containerSize, totalItemsSize, maxSpaceBetweenItems, spacesToGive).Reverse ().ToArray ();
+ }
+
+ break;
+
+ case Alignment.Fill:
+ switch (alignmentMode & ~AlignmentModes.AddSpaceBetweenItems)
+ {
+ case AlignmentModes.StartToEnd:
+ return Fill (in sizesCopy, containerSize, totalItemsSize);
+
+ case AlignmentModes.EndToStart:
+ return Fill (in sizesCopy, containerSize, totalItemsSize).Reverse ().ToArray ();
+ }
+
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException (nameof (alignment), alignment, null);
+ }
+
+ return [];
+ }
+
+ internal static int [] Start (ref readonly int [] sizes, int maxSpaceBetweenItems, int spacesToGive)
+ {
+ var positions = new int [sizes.Length]; // positions of the items. the return value.
+
+ for (var i = 0; i < sizes.Length; i++)
+ {
+ CheckSizeCannotBeNegative (i, in sizes);
+
+ if (i == 0)
+ {
+ positions [0] = 0; // first item position
+
+ continue;
+ }
+
+ int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0;
+
+ // subsequent items are placed one space after the previous item
+ positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore;
+ }
+
+ return positions;
+ }
+
+ internal static int [] IgnoreFirst (
+ ref readonly int [] sizes,
+ int containerSize,
+ int totalItemsSize,
+ int maxSpaceBetweenItems,
+ int spacesToGive
+ )
+ {
+ var positions = new int [sizes.Length]; // positions of the items. the return value.
+
+ if (sizes.Length > 1)
+ {
+ var currentPosition = 0;
+ positions [0] = currentPosition; // first item is flush left
+
+ for (int i = sizes.Length - 1; i >= 0; i--)
+ {
+ CheckSizeCannotBeNegative (i, in sizes);
+
+ if (i == sizes.Length - 1)
+ {
+ // start at right
+ currentPosition = Math.Max (totalItemsSize, containerSize) - sizes [i];
+ positions [i] = currentPosition;
+ }
+
+ if (i < sizes.Length - 1 && i > 0)
+ {
+ int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0;
+
+ positions [i] = currentPosition - sizes [i] - spaceBefore;
+ currentPosition = positions [i];
+ }
+ }
+ }
+ else if (sizes.Length == 1)
+ {
+ CheckSizeCannotBeNegative (0, in sizes);
+ positions [0] = 0; // single item is flush left
+ }
+
+ return positions;
+ }
+
+ internal static int [] IgnoreLast (
+ ref readonly int [] sizes,
+ int containerSize,
+ int totalItemsSize,
+ int maxSpaceBetweenItems,
+ int spacesToGive
+ )
+ {
+ var positions = new int [sizes.Length]; // positions of the items. the return value.
+
+ if (sizes.Length > 1)
+ {
+ var currentPosition = 0;
+ if (totalItemsSize > containerSize)
+ {
+ currentPosition = containerSize - totalItemsSize - spacesToGive;
+ }
+
+ for (var i = 0; i < sizes.Length; i++)
+ {
+ CheckSizeCannotBeNegative (i, in sizes);
+
+ if (i < sizes.Length - 1)
+ {
+ int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0;
+
+ positions [i] = currentPosition;
+ currentPosition += sizes [i] + spaceBefore;
+ }
+ }
+
+ positions [sizes.Length - 1] = containerSize - sizes [^1];
+ }
+ else if (sizes.Length == 1)
+ {
+ CheckSizeCannotBeNegative (0, in sizes);
+
+ positions [0] = containerSize - sizes [0]; // single item is flush right
+ }
+
+ return positions;
+ }
+
+ internal static int [] Fill (ref readonly int [] sizes, int containerSize, int totalItemsSize)
+ {
+ var positions = new int [sizes.Length]; // positions of the items. the return value.
+
+ int spaceBetween = sizes.Length > 1 ? (containerSize - totalItemsSize) / (sizes.Length - 1) : 0;
+ int remainder = sizes.Length > 1 ? (containerSize - totalItemsSize) % (sizes.Length - 1) : 0;
+ var currentPosition = 0;
+
+ for (var i = 0; i < sizes.Length; i++)
+ {
+ CheckSizeCannotBeNegative (i, in sizes);
+ positions [i] = currentPosition;
+ int extraSpace = i < remainder ? 1 : 0;
+ currentPosition += sizes [i] + spaceBetween + extraSpace;
+ }
+
+ return positions;
+ }
+
+ internal static int [] Center (ref readonly int [] sizes, int containerSize, int totalItemsSize, int maxSpaceBetweenItems, int spacesToGive)
+ {
+ var positions = new int [sizes.Length]; // positions of the items. the return value.
+
+ if (sizes.Length > 1)
+ {
+ // remaining space to be distributed before first and after the items
+ int remainingSpace = containerSize - totalItemsSize - spacesToGive;
+
+ for (var i = 0; i < sizes.Length; i++)
+ {
+ CheckSizeCannotBeNegative (i, in sizes);
+
+ if (i == 0)
+ {
+ positions [i] = remainingSpace / 2; // first item position
+
+ continue;
+ }
+
+ int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0;
+
+ // subsequent items are placed one space after the previous item
+ positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore;
+ }
+ }
+ else if (sizes.Length == 1)
+ {
+ CheckSizeCannotBeNegative (0, in sizes);
+ positions [0] = (containerSize - sizes [0]) / 2; // single item is centered
+ }
+
+ return positions;
+ }
+
+ internal static int [] End (ref readonly int [] sizes, int containerSize, int totalItemsSize, int maxSpaceBetweenItems, int spacesToGive)
+ {
+ var positions = new int [sizes.Length]; // positions of the items. the return value.
+ int currentPosition = containerSize - totalItemsSize - spacesToGive;
+
+ for (var i = 0; i < sizes.Length; i++)
+ {
+ CheckSizeCannotBeNegative (i, in sizes);
+ int spaceBefore = spacesToGive-- > 0 ? maxSpaceBetweenItems : 0;
+
+ positions [i] = currentPosition;
+ currentPosition += sizes [i] + spaceBefore;
+ }
+
+ return positions;
+ }
+
+ private static void CheckSizeCannotBeNegative (int i, ref readonly int [] sizes)
+ {
+ if (sizes [i] < 0)
+ {
+ throw new ArgumentException ("The size of an item cannot be negative.");
+ }
+ }
+}
diff --git a/Terminal.Gui/Drawing/Alignment.cs b/Terminal.Gui/Drawing/Alignment.cs
new file mode 100644
index 000000000..40061a8c1
--- /dev/null
+++ b/Terminal.Gui/Drawing/Alignment.cs
@@ -0,0 +1,82 @@
+using Terminal.Gui.Analyzers.Internal.Attributes;
+
+namespace Terminal.Gui;
+
+///
+/// Determines the position of items when arranged in a container.
+///
+[GenerateEnumExtensionMethods (FastHasFlags = true)]
+
+public enum Alignment
+{
+ ///
+ /// The items will be aligned to the start (left or top) of the container.
+ ///
+ ///
+ ///
+ /// If the container is smaller than the total size of the items, the end items will be clipped (their locations
+ /// will be greater than the container size).
+ ///
+ ///
+ /// The enumeration provides additional options for aligning items in a container.
+ ///
+ ///
+ ///
+ ///
+ /// |111 2222 33333 |
+ ///
+ ///
+ Start = 0,
+
+ ///
+ /// The items will be aligned to the end (right or bottom) of the container.
+ ///
+ ///
+ ///
+ /// If the container is smaller than the total size of the items, the start items will be clipped (their locations
+ /// will be negative).
+ ///
+ ///
+ /// The enumeration provides additional options for aligning items in a container.
+ ///
+ ///
+ ///
+ ///
+ /// | 111 2222 33333|
+ ///
+ ///
+ End,
+
+ ///
+ /// Center in the available space.
+ ///
+ ///
+ ///
+ /// If centering is not possible, the group will be left-aligned.
+ ///
+ ///
+ /// Extra space will be distributed between the items, biased towards the left.
+ ///
+ ///
+ ///
+ ///
+ /// | 111 2222 33333 |
+ ///
+ ///
+ Center,
+
+ ///
+ /// The items will fill the available space.
+ ///
+ ///
+ ///
+ /// Extra space will be distributed between the items, biased towards the end.
+ ///
+ ///
+ ///
+ ///
+ /// |111 2222 33333|
+ ///
+ ///
+ Fill,
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Drawing/AlignmentModes.cs b/Terminal.Gui/Drawing/AlignmentModes.cs
new file mode 100644
index 000000000..4de4d5c98
--- /dev/null
+++ b/Terminal.Gui/Drawing/AlignmentModes.cs
@@ -0,0 +1,52 @@
+using Terminal.Gui.Analyzers.Internal.Attributes;
+
+namespace Terminal.Gui;
+
+///
+/// Determines alignment modes for .
+///
+[Flags]
+[GenerateEnumExtensionMethods (FastHasFlags = true)]
+public enum AlignmentModes
+{
+ ///
+ /// The items will be arranged from start (left/top) to end (right/bottom).
+ ///
+ StartToEnd = 0,
+
+ ///
+ /// The items will be arranged from end (right/bottom) to start (left/top).
+ ///
+ ///
+ /// Not implemented.
+ ///
+ EndToStart = 1,
+
+ ///
+ /// At least one space will be added between items. Useful for justifying text where at least one space is needed.
+ ///
+ ///
+ ///
+ /// If the total size of the items is greater than the container size, the space between items will be ignored
+ /// starting from the end.
+ ///
+ ///
+ AddSpaceBetweenItems = 2,
+
+ ///
+ /// When aligning via or , the item opposite to the alignment (the first or last item) will be ignored.
+ ///
+ ///
+ ///
+ /// If the container is smaller than the total size of the items, the end items will be clipped (their locations
+ /// will be greater than the container size).
+ ///
+ ///
+ ///
+ ///
+ /// Start: |111 2222 33333|
+ /// End: |111 2222 33333|
+ ///
+ ///
+ IgnoreFirstOrLast = 4,
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Drawing/Justification.cs b/Terminal.Gui/Drawing/Justification.cs
deleted file mode 100644
index f1fba56a8..000000000
--- a/Terminal.Gui/Drawing/Justification.cs
+++ /dev/null
@@ -1,333 +0,0 @@
-namespace Terminal.Gui;
-
-///
-/// Controls how the justifies items within a container.
-///
-public enum Justification
-{
- ///
- /// The items will be aligned to the left.
- /// Set to to ensure at least one space between
- /// each item.
- ///
- ///
- ///
- /// 111 2222 33333
- ///
- ///
- Left,
-
- ///
- /// The items will be aligned to the right.
- /// Set to to ensure at least one space between
- /// each item.
- ///
- ///
- ///
- /// 111 2222 33333
- ///
- ///
- Right,
-
- ///
- /// The group will be centered in the container.
- /// If centering is not possible, the group will be left-justified.
- /// Set to to ensure at least one space between
- /// each item.
- ///
- ///
- ///
- /// 111 2222 33333
- ///
- ///
- Centered,
-
- ///
- /// The items will be justified. Space will be added between the items such that the first item
- /// is at the start and the right side of the last item against the end.
- /// Set to to ensure at least one space between
- /// each item.
- ///
- ///
- ///
- /// 111 2222 33333
- ///
- ///
- Justified,
-
- ///
- /// The first item will be aligned to the left and the remaining will aligned to the right.
- /// Set to to ensure at least one space between
- /// each item.
- ///
- ///
- ///
- /// 111 2222 33333
- ///
- ///
- FirstLeftRestRight,
-
- ///
- /// The last item will be aligned to the right and the remaining will aligned to the left.
- /// Set to to ensure at least one space between
- /// each item.
- ///
- ///
- ///
- /// 111 2222 33333
- ///
- ///
- LastRightRestLeft
-}
-
-///
-/// Justifies items within a container based on the specified .
-///
-public class Justifier
-{
- ///
- /// Gets or sets how the justifies items within a container.
- ///
- public Justification Justification { get; set; }
-
- ///
- /// The size of the container.
- ///
- public int ContainerSize { get; set; }
-
- ///
- /// Gets or sets whether puts a space is placed between items. Default is . If , a space will be
- /// placed between each item, which is useful for justifying text.
- ///
- public bool PutSpaceBetweenItems { get; set; }
-
- ///
- /// Takes a list of items and returns their positions when justified within a container wide based on the specified
- /// .
- ///
- /// The sizes of the items to justify.
- /// The locations of the items, from left to right.
- public int [] Justify (int [] sizes)
- {
- return Justify (Justification, PutSpaceBetweenItems, ContainerSize, sizes);
- }
-
- ///
- /// Takes a list of items and returns their positions when justified within a container wide based on the specified
- /// .
- ///
- /// The sizes of the items to justify.
- /// The justification style.
- ///
- /// The size of the container.
- /// The locations of the items, from left to right.
- public static int [] Justify (Justification justification, bool putSpaceBetweenItems, int containerSize, int [] sizes)
- {
- if (sizes.Length == 0)
- {
- return new int [] { };
- }
-
- int maxSpaceBetweenItems = putSpaceBetweenItems ? 1 : 0;
-
- var positions = new int [sizes.Length]; // positions of the items. the return value.
- int totalItemsSize = sizes.Sum ();
- int totalGaps = sizes.Length - 1; // total gaps between items
- int totalItemsAndSpaces = totalItemsSize + totalGaps * maxSpaceBetweenItems; // total size of items and spaces if we had enough room
-
- int spaces = totalGaps * maxSpaceBetweenItems; // We'll decrement this below to place one space between each item until we run out
- if (totalItemsSize >= containerSize)
- {
- spaces = 0;
- }
- else if (totalItemsAndSpaces > containerSize)
- {
- spaces = containerSize - totalItemsSize;
- }
-
- switch (justification)
- {
- case Justification.Left:
- var currentPosition = 0;
-
- for (var i = 0; i < sizes.Length; i++)
- {
- if (sizes [i] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- if (i == 0)
- {
- positions [0] = 0; // first item position
-
- continue;
- }
-
- int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
-
- // subsequent items are placed one space after the previous item
- positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore;
- }
-
- break;
- case Justification.Right:
- currentPosition = Math.Max (0, containerSize - totalItemsSize - spaces);
-
- for (var i = 0; i < sizes.Length; i++)
- {
- if (sizes [i] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
-
- positions [i] = currentPosition;
- currentPosition += sizes [i] + spaceBefore;
- }
-
- break;
-
- case Justification.Centered:
- if (sizes.Length > 1)
- {
- // remaining space to be distributed before first and after the items
- int remainingSpace = Math.Max (0, containerSize - totalItemsSize - spaces);
-
- for (var i = 0; i < sizes.Length; i++)
- {
- if (sizes [i] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- if (i == 0)
- {
- positions [i] = remainingSpace / 2; // first item position
-
- continue;
- }
-
- int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
-
- // subsequent items are placed one space after the previous item
- positions [i] = positions [i - 1] + sizes [i - 1] + spaceBefore;
- }
- }
- else if (sizes.Length == 1)
- {
- if (sizes [0] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- positions [0] = (containerSize - sizes [0]) / 2; // single item is centered
- }
-
- break;
-
- case Justification.Justified:
- int spaceBetween = sizes.Length > 1 ? (containerSize - totalItemsSize) / (sizes.Length - 1) : 0;
- int remainder = sizes.Length > 1 ? (containerSize - totalItemsSize) % (sizes.Length - 1) : 0;
- currentPosition = 0;
-
- for (var i = 0; i < sizes.Length; i++)
- {
- if (sizes [i] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- positions [i] = currentPosition;
- int extraSpace = i < remainder ? 1 : 0;
- currentPosition += sizes [i] + spaceBetween + extraSpace;
- }
-
- break;
-
- // 111 2222 33333
- case Justification.LastRightRestLeft:
- if (sizes.Length > 1)
- {
- currentPosition = 0;
-
- for (var i = 0; i < sizes.Length; i++)
- {
- if (sizes [i] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- if (i < sizes.Length - 1)
- {
- int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
-
- positions [i] = currentPosition;
- currentPosition += sizes [i] + spaceBefore;
- }
- }
-
- positions [sizes.Length - 1] = containerSize - sizes [sizes.Length - 1];
- }
- else if (sizes.Length == 1)
- {
- if (sizes [0] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- positions [0] = containerSize - sizes [0]; // single item is flush right
- }
-
- break;
-
- // 111 2222 33333
- case Justification.FirstLeftRestRight:
- if (sizes.Length > 1)
- {
- currentPosition = 0;
- positions [0] = currentPosition; // first item is flush left
-
- for (int i = sizes.Length - 1; i >= 0; i--)
- {
- if (sizes [i] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- if (i == sizes.Length - 1)
- {
- // start at right
- currentPosition = containerSize - sizes [i];
- positions [i] = currentPosition;
- }
-
- if (i < sizes.Length - 1 && i > 0)
- {
- int spaceBefore = spaces-- > 0 ? maxSpaceBetweenItems : 0;
-
- positions [i] = currentPosition - sizes [i] - spaceBefore;
- currentPosition = positions [i];
- }
- }
- }
- else if (sizes.Length == 1)
- {
- if (sizes [0] < 0)
- {
- throw new ArgumentException ("The size of an item cannot be negative.");
- }
-
- positions [0] = 0; // single item is flush left
- }
-
- break;
-
- default:
- throw new ArgumentOutOfRangeException (nameof (justification), justification, null);
- }
-
- return positions;
- }
-}
diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs
index 6070e6cbe..ad684470b 100644
--- a/Terminal.Gui/Drawing/Thickness.cs
+++ b/Terminal.Gui/Drawing/Thickness.cs
@@ -230,8 +230,8 @@ public class Thickness : IEquatable
var tf = new TextFormatter
{
Text = label is null ? string.Empty : $"{label} {this}",
- Alignment = TextAlignment.Centered,
- VerticalAlignment = VerticalTextAlignment.Bottom,
+ Alignment = Alignment.Center,
+ VerticalAlignment = Alignment.End,
AutoSize = true
};
tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect);
diff --git a/Terminal.Gui/Input/CommandContext.cs b/Terminal.Gui/Input/CommandContext.cs
new file mode 100644
index 000000000..5d3269226
--- /dev/null
+++ b/Terminal.Gui/Input/CommandContext.cs
@@ -0,0 +1,41 @@
+#nullable enable
+namespace Terminal.Gui;
+///
+/// Provides context for a that is being invoked.
+///
+///
+/// To define a that is invoked with context,
+/// use
+///
+///
+public record struct CommandContext
+{
+ ///
+ /// Initializes a new instance of with the specified ,
+ ///
+ ///
+ ///
+ ///
+ public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null)
+ {
+ Command = command;
+ Key = key;
+ KeyBinding = keyBinding;
+ }
+
+ ///
+ /// The that is being invoked.
+ ///
+ public Command Command { get; set; }
+
+ ///
+ /// The that is being invoked. This is the key that was pressed to invoke the .
+ ///
+ public Key? Key { get; set; }
+
+ ///
+ /// The KeyBinding that was used to invoke the , if any.
+ ///
+ public KeyBinding? KeyBinding { get; set; }
+}
diff --git a/Terminal.Gui/Input/KeyBinding.cs b/Terminal.Gui/Input/KeyBinding.cs
index b92eebc74..baac07384 100644
--- a/Terminal.Gui/Input/KeyBinding.cs
+++ b/Terminal.Gui/Input/KeyBinding.cs
@@ -1,260 +1,34 @@
-// These classes use a key binding system based on the design implemented in Scintilla.Net which is an
+#nullable enable
+
+// These classes use a key binding system based on the design implemented in Scintilla.Net which is an
// MIT licensed open source project https://github.com/jacobslusser/ScintillaNET/blob/master/src/ScintillaNET/Command.cs
namespace Terminal.Gui;
///
-/// Defines the scope of a that has been bound to a key with
-/// .
+/// Provides a collection of objects that are scoped to .
///
-///
-/// Key bindings are scoped to the most-focused view () by default.
-///
-[Flags]
-public enum KeyBindingScope
-{
- /// The key binding is scoped to just the view that has focus.
- Focused = 1,
-
- ///
- /// The key binding is scoped to the View's SuperView and will be triggered even when the View does not have focus, as
- /// long as the SuperView does have focus. This is typically used for s.
- ///
- ///
- /// Use for Views such as MenuBar and StatusBar which provide commands (shortcuts etc...) that trigger even
- /// when not focused.
- ///
- ///
- /// HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
- /// any of its subviews.
- ///
- ///
- ///
- HotKey = 2,
-
- ///
- /// The key binding will be triggered regardless of which view has focus. This is typically used for global
- /// commands.
- ///
- ///
- /// Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or
- /// any of its subviews, and if the key down event was not bound to a .
- ///
- Application = 4
-}
-
-/// Provides a collection of objects that are scoped to .
-public class KeyBinding
+public record struct KeyBinding
{
/// Initializes a new instance.
- ///
- ///
- public KeyBinding (Command [] commands, KeyBindingScope scope)
+ /// The commands this key binding will invoke.
+ /// The scope of the .
+ /// Arbitrary context that can be associated with this key binding.
+ public KeyBinding (Command [] commands, KeyBindingScope scope, object? context = null)
{
Commands = commands;
Scope = scope;
+ Context = context;
}
- /// The actions which can be performed by the application or bound to keys in a control.
+ /// The commands this key binding will invoke.
public Command [] Commands { get; set; }
- /// The scope of the bound to a key.
+ /// The scope of the .
public KeyBindingScope Scope { get; set; }
-}
-
-/// A class that provides a collection of objects bound to a .
-public class KeyBindings
-{
- // TODO: Add a dictionary comparer that ignores Scope
- /// The collection of objects.
- public Dictionary Bindings { get; } = new ();
-
- /// Adds a to the collection.
- ///
- ///
- public void Add (Key key, KeyBinding binding) { Bindings.Add (key, binding); }
-
- ///
- /// Adds a new key combination that will trigger the commands in .
- ///
- /// If the key is already bound to a different array of s it will be rebound
- /// .
- ///
- ///
- ///
- /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch
- /// focus to another view and perform multiple commands there).
- ///
- /// The key to check.
- /// The scope for the command.
- ///
- /// The command to invoked on the when is pressed. When
- /// multiple commands are provided,they will be applied in sequence. The bound strike will be
- /// consumed if any took effect.
- ///
- public void Add (Key key, KeyBindingScope scope, params Command [] commands)
- {
- if (key is null || !key.IsValid)
- {
- //throw new ArgumentException ("Invalid Key", nameof (commands));
- return;
- }
-
- if (commands.Length == 0)
- {
- throw new ArgumentException (@"At least one command must be specified", nameof (commands));
- }
-
- if (TryGet (key, out KeyBinding _))
- {
- Bindings [key] = new KeyBinding (commands, scope);
- }
- else
- {
- Bindings.Add (key, new KeyBinding (commands, scope));
- }
- }
-
- ///
- ///
- /// Adds a new key combination that will trigger the commands in (if supported by the
- /// View - see ).
- ///
- ///
- /// This is a helper function for for
- /// scoped commands.
- ///
- ///
- /// If the key is already bound to a different array of s it will be rebound
- /// .
- ///
- ///
- ///
- /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch
- /// focus to another view and perform multiple commands there).
- ///
- /// The key to check.
- ///
- /// The command to invoked on the when is pressed. When
- /// multiple commands are provided,they will be applied in sequence. The bound strike will be
- /// consumed if any took effect.
- ///
- public void Add (Key key, params Command [] commands) { Add (key, KeyBindingScope.Focused, commands); }
-
- /// Removes all objects from the collection.
- public void Clear () { Bindings.Clear (); }
-
- ///
- /// Removes all key bindings that trigger the given command set. Views can have multiple different keys bound to
- /// the same command sets and this method will clear all of them.
- ///
- ///
- public void Clear (params Command [] command)
- {
- var kvps = Bindings
- .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
- .ToArray ();
- foreach (KeyValuePair kvp in kvps)
- {
- Bindings.Remove (kvp.Key);
- }
- }
-
- /// Gets the for the specified .
- ///
- ///
- public KeyBinding Get (Key key) { return TryGet (key, out KeyBinding binding) ? binding : null; }
-
- /// Gets the for the specified .
- ///
- ///
- ///
- public KeyBinding Get (Key key, KeyBindingScope scope) { return TryGet (key, scope, out KeyBinding binding) ? binding : null; }
-
- /// Gets the array of s bound to if it exists.
- /// The key to check.
- ///
- /// The array of s if is bound. An empty array
- /// if not.
- ///
- public Command [] GetCommands (Key key)
- {
- if (TryGet (key, out KeyBinding bindings))
- {
- return bindings.Commands;
- }
-
- return Array.Empty ();
- }
-
- /// Gets the Key used by a set of commands.
- ///
- /// The set of commands to search.
- /// The used by a
- /// If no matching set of commands was found.
- public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; }
-
- /// Removes a from the collection.
- ///
- public void Remove (Key key) { Bindings.Remove (key); }
-
- /// Replaces a key combination already bound to a set of s.
- ///
- /// The key to be replaced.
- /// The new key to be used.
- public void Replace (Key fromKey, Key toKey)
- {
- if (!TryGet (fromKey, out KeyBinding _))
- {
- return;
- }
-
- KeyBinding value = Bindings [fromKey];
- Bindings.Remove (fromKey);
- Bindings [toKey] = value;
- }
-
- /// Gets the commands bound with the specified Key.
- ///
- /// The key to check.
- ///
- /// When this method returns, contains the commands bound with the specified Key, if the Key is
- /// found; otherwise, null. This parameter is passed uninitialized.
- ///
- /// if the Key is bound; otherwise .
- public bool TryGet (Key key, out KeyBinding binding)
- {
- if (key.IsValid)
- {
- return Bindings.TryGetValue (key, out binding);
- }
-
- binding = new KeyBinding (Array.Empty (), KeyBindingScope.Focused);
-
- return false;
- }
-
- /// Gets the commands bound with the specified Key that are scoped to a particular scope.
- ///
- /// The key to check.
- /// the scope to filter on
- ///
- /// When this method returns, contains the commands bound with the specified Key, if the Key is
- /// found; otherwise, null. This parameter is passed uninitialized.
- ///
- /// if the Key is bound; otherwise .
- public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding)
- {
- if (key.IsValid && Bindings.TryGetValue (key, out binding))
- {
- if (scope.HasFlag (binding.Scope))
- {
- return true;
- }
- }
-
- binding = new KeyBinding (Array.Empty (), KeyBindingScope.Focused);
-
- return false;
- }
+
+ ///
+ /// Arbitrary context that can be associated with this key binding.
+ ///
+ public object? Context { get; set; }
}
diff --git a/Terminal.Gui/Input/KeyBindingScope.cs b/Terminal.Gui/Input/KeyBindingScope.cs
new file mode 100644
index 000000000..9799fc831
--- /dev/null
+++ b/Terminal.Gui/Input/KeyBindingScope.cs
@@ -0,0 +1,46 @@
+using Terminal.Gui.Analyzers.Internal.Attributes;
+
+namespace Terminal.Gui;
+
+///
+/// Defines the scope of a that has been bound to a key with
+/// .
+///
+///
+/// Key bindings are scoped to the most-focused view () by default.
+///
+[Flags]
+[GenerateEnumExtensionMethods (FastHasFlags = true)]
+public enum KeyBindingScope
+{
+ /// The key binding is scoped to just the view that has focus.
+ Focused = 1,
+
+ ///
+ /// The key binding is scoped to the View's Superview hierarchy and will be triggered even when the View does not have
+ /// focus, as
+ /// long as the SuperView does have focus. This is typically used for s.
+ ///
+ ///
+ /// The View must be visible.
+ ///
+ ///
+ /// HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
+ /// any of its subviews.
+ ///
+ ///
+ ///
+ HotKey = 2,
+
+ ///
+ /// The key binding will be triggered regardless of which view has focus. This is typically used for global
+ /// commands, which are called Shortcuts.
+ ///
+ ///
+ ///
+ /// Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or
+ /// any of its subviews, and if the key was not bound to a .
+ ///
+ ///
+ Application = 4
+}
diff --git a/Terminal.Gui/Input/KeyBindings.cs b/Terminal.Gui/Input/KeyBindings.cs
new file mode 100644
index 000000000..8ec38329e
--- /dev/null
+++ b/Terminal.Gui/Input/KeyBindings.cs
@@ -0,0 +1,258 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+///
+/// Provides a collection of objects bound to a .
+///
+public class KeyBindings
+{
+ ///
+ /// Initializes a new instance. This constructor is used when the are not bound to a
+ /// , such as in unit tests.
+ ///
+ public KeyBindings () { }
+
+ /// Initializes a new instance bound to .
+ public KeyBindings (View boundView) { BoundView = boundView; }
+
+ ///
+ /// The view that the are bound to.
+ ///
+ public View? BoundView { get; }
+
+ // TODO: Add a dictionary comparer that ignores Scope
+ // TODO: This should not be public!
+ /// The collection of objects.
+ public Dictionary Bindings { get; } = new ();
+
+ /// Adds a to the collection.
+ ///
+ ///
+ public void Add (Key key, KeyBinding binding)
+ {
+ if (TryGet (key, out KeyBinding _))
+ {
+ Bindings [key] = binding;
+ }
+ else
+ {
+ Bindings.Add (key, binding);
+ if (binding.Scope.FastHasFlags (KeyBindingScope.Application))
+ {
+ Application.AddKeyBinding (key, BoundView);
+ }
+ }
+ }
+
+ ///
+ /// Adds a new key combination that will trigger the commands in .
+ ///
+ /// If the key is already bound to a different array of s it will be rebound
+ /// .
+ ///
+ ///
+ ///
+ /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch
+ /// focus to another view and perform multiple commands there).
+ ///
+ /// The key to check.
+ /// The scope for the command.
+ ///
+ /// The command to invoked on the when is pressed. When
+ /// multiple commands are provided,they will be applied in sequence. The bound strike will be
+ /// consumed if any took effect.
+ ///
+ public void Add (Key key, KeyBindingScope scope, params Command [] commands)
+ {
+ if (key is null || !key.IsValid)
+ {
+ //throw new ArgumentException ("Invalid Key", nameof (commands));
+ return;
+ }
+
+ if (commands.Length == 0)
+ {
+ throw new ArgumentException (@"At least one command must be specified", nameof (commands));
+ }
+
+ if (TryGet (key, out KeyBinding _))
+ {
+ Bindings [key] = new (commands, scope);
+ }
+ else
+ {
+ Add (key, new KeyBinding (commands, scope));
+ }
+ }
+
+ ///
+ ///
+ /// Adds a new key combination that will trigger the commands in (if supported by the
+ /// View - see ).
+ ///
+ ///
+ /// This is a helper function for for
+ /// scoped commands.
+ ///
+ ///
+ /// If the key is already bound to a different array of s it will be rebound
+ /// .
+ ///
+ ///
+ ///
+ /// Commands are only ever applied to the current (i.e. this feature cannot be used to switch
+ /// focus to another view and perform multiple commands there).
+ ///
+ /// The key to check.
+ ///
+ /// The command to invoked on the when is pressed. When
+ /// multiple commands are provided,they will be applied in sequence. The bound strike will be
+ /// consumed if any took effect.
+ ///
+ public void Add (Key key, params Command [] commands)
+ {
+ Add (key, KeyBindingScope.Focused, commands);
+ }
+
+ /// Removes all objects from the collection.
+ public void Clear ()
+ {
+ Application.ClearKeyBindings (BoundView);
+
+ Bindings.Clear ();
+ }
+
+ ///
+ /// Removes all key bindings that trigger the given command set. Views can have multiple different keys bound to
+ /// the same command sets and this method will clear all of them.
+ ///
+ ///
+ public void Clear (params Command [] command)
+ {
+ KeyValuePair [] kvps = Bindings
+ .Where (kvp => kvp.Value.Commands.SequenceEqual (command))
+ .ToArray ();
+
+ foreach (KeyValuePair kvp in kvps)
+ {
+ Remove (kvp.Key);
+ }
+ }
+
+ /// Gets the for the specified .
+ ///
+ ///
+ public KeyBinding Get (Key key)
+ {
+ if (TryGet (key, out KeyBinding binding))
+ {
+ return binding;
+ }
+ throw new InvalidOperationException ($"Key {key} is not bound.");
+ }
+
+ /// Gets the for the specified .
+ ///
+ ///
+ ///
+ public KeyBinding Get (Key key, KeyBindingScope scope)
+ {
+ if (TryGet (key, scope, out KeyBinding binding))
+ {
+ return binding;
+ }
+ throw new InvalidOperationException ($"Key {key}/{scope} is not bound.");
+ }
+
+ /// Gets the array of s bound to if it exists.
+ /// The key to check.
+ ///
+ /// The array of s if is bound. An empty array
+ /// if not.
+ ///
+ public Command [] GetCommands (Key key)
+ {
+ if (TryGet (key, out KeyBinding bindings))
+ {
+ return bindings.Commands;
+ }
+
+ return Array.Empty ();
+ }
+
+ /// Gets the Key used by a set of commands.
+ ///
+ /// The set of commands to search.
+ /// The used by a
+ /// If no matching set of commands was found.
+ public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; }
+
+ /// Removes a from the collection.
+ ///
+ public void Remove (Key key)
+ {
+ Bindings.Remove (key);
+ Application.RemoveKeyBinding (key, BoundView);
+ }
+
+ /// Replaces a key combination already bound to a set of s.
+ ///
+ /// The key to be replaced.
+ /// The new key to be used.
+ public void Replace (Key oldKey, Key newKey)
+ {
+ if (!TryGet (oldKey, out KeyBinding _))
+ {
+ return;
+ }
+
+ KeyBinding value = Bindings [oldKey];
+ Remove (oldKey);
+ Add (newKey, value);
+ }
+
+ /// Gets the commands bound with the specified Key.
+ ///
+ /// The key to check.
+ ///
+ /// When this method returns, contains the commands bound with the specified Key, if the Key is
+ /// found; otherwise, null. This parameter is passed uninitialized.
+ ///
+ /// if the Key is bound; otherwise .
+ public bool TryGet (Key key, out KeyBinding binding)
+ {
+ if (key.IsValid)
+ {
+ return Bindings.TryGetValue (key, out binding);
+ }
+
+ binding = new (Array.Empty (), KeyBindingScope.Focused);
+
+ return false;
+ }
+
+ /// Gets the commands bound with the specified Key that are scoped to a particular scope.
+ ///
+ /// The key to check.
+ /// the scope to filter on
+ ///
+ /// When this method returns, contains the commands bound with the specified Key, if the Key is
+ /// found; otherwise, null. This parameter is passed uninitialized.
+ ///
+ /// if the Key is bound; otherwise .
+ public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding)
+ {
+ if (key.IsValid && Bindings.TryGetValue (key, out binding))
+ {
+ if (scope.HasFlag (binding.Scope))
+ {
+ return true;
+ }
+ }
+
+ binding = new (Array.Empty (), KeyBindingScope.Focused);
+
+ return false;
+ }
+}
diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json
index 368ccd8bf..8380a14f5 100644
--- a/Terminal.Gui/Resources/config.json
+++ b/Terminal.Gui/Resources/config.json
@@ -24,7 +24,8 @@
"Themes": [
{
"Default": {
- "Dialog.DefaultButtonAlignment": "Center",
+ "Dialog.DefaultButtonAlignment": "End",
+ "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
"FrameView.DefaultBorderStyle": "Single",
"Window.DefaultBorderStyle": "Single",
"ColorSchemes": [
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index f0840c041..a8b1f64ba 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -136,5 +136,6 @@
truetrueMiguel de Icaza, Tig Kindel (@tig), @BDisp
+ true
\ No newline at end of file
diff --git a/Terminal.Gui/Text/TextAlignment.cs b/Terminal.Gui/Text/TextAlignment.cs
deleted file mode 100644
index 44950cfd5..000000000
--- a/Terminal.Gui/Text/TextAlignment.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Terminal.Gui;
-
-/// Text alignment enumeration, controls how text is displayed.
-public enum TextAlignment
-{
- /// The text will be left-aligned.
- Left,
-
- /// The text will be right-aligned.
- Right,
-
- /// The text will be centered horizontally.
- Centered,
-
- ///
- /// The text will be justified (spaces will be added to existing spaces such that the text fills the container
- /// horizontally).
- ///
- Justified
-}
\ No newline at end of file
diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs
index af4a7b97b..bee37de67 100644
--- a/Terminal.Gui/Text/TextFormatter.cs
+++ b/Terminal.Gui/Text/TextFormatter.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics;
+
namespace Terminal.Gui;
///
@@ -15,14 +17,14 @@ public class TextFormatter
private Size _size;
private int _tabWidth = 4;
private string _text;
- private TextAlignment _textAlignment;
+ private Alignment _textAlignment = Alignment.Start;
private TextDirection _textDirection;
- private VerticalTextAlignment _textVerticalAlignment;
+ private Alignment _textVerticalAlignment = Alignment.Start;
private bool _wordWrap = true;
- /// Controls the horizontal text-alignment property.
+ /// Get or sets the horizontal text alignment.
/// The text alignment.
- public TextAlignment Alignment
+ public Alignment Alignment
{
get => _textAlignment;
set => _textAlignment = EnableNeedsFormat (value);
@@ -32,8 +34,7 @@ public class TextFormatter
///
/// Used when is using to resize the view's to fit .
///
- /// AutoSize is ignored if and
- /// are used.
+ /// AutoSize is ignored if is used.
///
///
public bool AutoSize
@@ -68,9 +69,8 @@ public class TextFormatter
/// Only the first HotKey specifier found in is supported.
///
///
- /// If (the default) the width required for the HotKey specifier is returned. Otherwise the
- /// height
- /// is returned.
+ /// If (the default) the width required for the HotKey specifier is returned. Otherwise, the
+ /// height is returned.
///
///
/// The number of characters required for the . If the text
@@ -97,8 +97,8 @@ public class TextFormatter
///
public int CursorPosition { get; internal set; }
- /// Controls the text-direction property.
- /// The text vertical alignment.
+ /// Gets or sets the text-direction.
+ /// The text direction.
public TextDirection Direction
{
get => _textDirection;
@@ -112,8 +112,7 @@ public class TextFormatter
}
}
}
-
-
+
///
/// Determines if the viewport width will be used or only the text width will be used,
/// If all the viewport area will be filled with whitespaces and the same background color
@@ -223,9 +222,9 @@ public class TextFormatter
}
}
- /// Controls the vertical text-alignment property.
+ /// Gets or sets the vertical text-alignment.
/// The text vertical alignment.
- public VerticalTextAlignment VerticalAlignment
+ public Alignment VerticalAlignment
{
get => _textVerticalAlignment;
set => _textVerticalAlignment = EnableNeedsFormat (value);
@@ -318,10 +317,10 @@ public class TextFormatter
// When text is justified, we lost left or right, so we use the direction to align.
- int x, y;
+ int x = 0, y = 0;
// Horizontal Alignment
- if (Alignment is TextAlignment.Right)
+ if (Alignment is Alignment.End)
{
if (isVertical)
{
@@ -336,7 +335,7 @@ public class TextFormatter
CursorPosition = screen.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0);
}
}
- else if (Alignment is TextAlignment.Left)
+ else if (Alignment is Alignment.Start)
{
if (isVertical)
{
@@ -352,7 +351,7 @@ public class TextFormatter
CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
}
- else if (Alignment is TextAlignment.Justified)
+ else if (Alignment is Alignment.Fill)
{
if (isVertical)
{
@@ -375,7 +374,7 @@ public class TextFormatter
CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0;
}
- else if (Alignment is TextAlignment.Centered)
+ else if (Alignment is Alignment.Center)
{
if (isVertical)
{
@@ -395,11 +394,13 @@ public class TextFormatter
}
else
{
- throw new ArgumentOutOfRangeException ($"{nameof (Alignment)}");
+ Debug.WriteLine ($"Unsupported Alignment: {nameof (VerticalAlignment)}");
+
+ return;
}
// Vertical Alignment
- if (VerticalAlignment is VerticalTextAlignment.Bottom)
+ if (VerticalAlignment is Alignment.End)
{
if (isVertical)
{
@@ -410,7 +411,7 @@ public class TextFormatter
y = screen.Bottom - linesFormatted.Count + line;
}
}
- else if (VerticalAlignment is VerticalTextAlignment.Top)
+ else if (VerticalAlignment is Alignment.Start)
{
if (isVertical)
{
@@ -421,7 +422,7 @@ public class TextFormatter
y = screen.Top + line;
}
}
- else if (VerticalAlignment is VerticalTextAlignment.Justified)
+ else if (VerticalAlignment is Alignment.Fill)
{
if (isVertical)
{
@@ -435,7 +436,7 @@ public class TextFormatter
line < linesFormatted.Count - 1 ? screen.Height - interval <= 1 ? screen.Top + 1 : screen.Top + line * interval : screen.Bottom - 1;
}
}
- else if (VerticalAlignment is VerticalTextAlignment.Middle)
+ else if (VerticalAlignment is Alignment.Center)
{
if (isVertical)
{
@@ -450,7 +451,9 @@ public class TextFormatter
}
else
{
- throw new ArgumentOutOfRangeException ($"{nameof (VerticalAlignment)}");
+ Debug.WriteLine ($"Unsupported Alignment: {nameof (VerticalAlignment)}");
+
+ return;
}
int colOffset = screen.X < 0 ? Math.Abs (screen.X) : 0;
@@ -471,8 +474,8 @@ public class TextFormatter
{
if (idx < 0
|| (isVertical
- ? VerticalAlignment != VerticalTextAlignment.Bottom && current < 0
- : Alignment != TextAlignment.Right && x + current + colOffset < 0))
+ ? VerticalAlignment != Alignment.End && current < 0
+ : Alignment != Alignment.End && x + current + colOffset < 0))
{
current++;
@@ -561,7 +564,7 @@ public class TextFormatter
if (HotKeyPos > -1 && idx == HotKeyPos)
{
- if ((isVertical && VerticalAlignment == VerticalTextAlignment.Justified) || (!isVertical && Alignment == TextAlignment.Justified))
+ if ((isVertical && VerticalAlignment == Alignment.Fill) || (!isVertical && Alignment == Alignment.Fill))
{
CursorPosition = idx - start;
}
@@ -699,7 +702,7 @@ public class TextFormatter
_lines = Format (
text,
Size.Height,
- VerticalAlignment == VerticalTextAlignment.Justified,
+ VerticalAlignment == Alignment.Fill,
Size.Width > colsWidth && WordWrap,
PreserveTrailingSpaces,
TabWidth,
@@ -723,7 +726,7 @@ public class TextFormatter
_lines = Format (
text,
Size.Width,
- Alignment == TextAlignment.Justified,
+ Alignment == Alignment.Fill,
Size.Height > 1 && WordWrap,
PreserveTrailingSpaces,
TabWidth,
@@ -977,7 +980,7 @@ public class TextFormatter
// if value is not wide enough
if (text.EnumerateRunes ().Sum (c => c.GetColumns ()) < width)
{
- // pad it out with spaces to the given alignment
+ // pad it out with spaces to the given Alignment
int toPad = width - text.EnumerateRunes ().Sum (c => c.GetColumns ());
return text + new string (' ', toPad);
@@ -999,7 +1002,7 @@ public class TextFormatter
/// instance to access any of his objects.
/// A list of word wrapped lines.
///
- /// This method does not do any justification.
+ /// This method does not do any alignment.
/// This method strips Newline ('\n' and '\r\n') sequences before processing.
///
/// If is at most one space will be preserved
@@ -1031,7 +1034,7 @@ public class TextFormatter
List runes = StripCRLF (text).ToRuneList ();
int start = Math.Max (
- !runes.Contains ((Rune)' ') && textFormatter is { VerticalAlignment: VerticalTextAlignment.Bottom } && IsVerticalDirection (textDirection)
+ !runes.Contains ((Rune)' ') && textFormatter is { VerticalAlignment: Alignment.End } && IsVerticalDirection (textDirection)
? runes.Count - width
: 0,
0);
@@ -1249,7 +1252,7 @@ public class TextFormatter
/// The number of columns to clip the text to. Text longer than will be
/// clipped.
///
- /// Alignment.
+ /// Alignment.
/// The text direction.
/// The number of columns used for a tab.
/// instance to access any of his objects.
@@ -1257,13 +1260,13 @@ public class TextFormatter
public static string ClipAndJustify (
string text,
int width,
- TextAlignment talign,
+ Alignment textAlignment,
TextDirection textDirection = TextDirection.LeftRight_TopBottom,
int tabWidth = 0,
TextFormatter textFormatter = null
)
{
- return ClipAndJustify (text, width, talign == TextAlignment.Justified, textDirection, tabWidth, textFormatter);
+ return ClipAndJustify (text, width, textAlignment == Alignment.Fill, textDirection, tabWidth, textFormatter);
}
/// Justifies text within a specified width.
@@ -1304,12 +1307,12 @@ public class TextFormatter
{
if (IsHorizontalDirection (textDirection))
{
- if (textFormatter is { Alignment: TextAlignment.Right })
+ if (textFormatter is { Alignment: Alignment.End })
{
return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection);
}
- if (textFormatter is { Alignment: TextAlignment.Centered })
+ if (textFormatter is { Alignment: Alignment.Center })
{
return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection);
}
@@ -1319,12 +1322,12 @@ public class TextFormatter
if (IsVerticalDirection (textDirection))
{
- if (textFormatter is { VerticalAlignment: VerticalTextAlignment.Bottom })
+ if (textFormatter is { VerticalAlignment: Alignment.End })
{
return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection);
}
- if (textFormatter is { VerticalAlignment: VerticalTextAlignment.Middle })
+ if (textFormatter is { VerticalAlignment: Alignment.Center })
{
return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection);
}
@@ -1342,14 +1345,14 @@ public class TextFormatter
if (IsHorizontalDirection (textDirection))
{
- if (textFormatter is { Alignment: TextAlignment.Right })
+ if (textFormatter is { Alignment: Alignment.End })
{
if (GetRuneWidth (text, tabWidth, textDirection) > width)
{
return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection);
}
}
- else if (textFormatter is { Alignment: TextAlignment.Centered })
+ else if (textFormatter is { Alignment: Alignment.Center })
{
return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection);
}
@@ -1361,14 +1364,14 @@ public class TextFormatter
if (IsVerticalDirection (textDirection))
{
- if (textFormatter is { VerticalAlignment: VerticalTextAlignment.Bottom })
+ if (textFormatter is { VerticalAlignment: Alignment.End })
{
if (runes.Count - zeroLength > width)
{
return GetRangeThatFits (runes, runes.Count - width, text, width, tabWidth, textDirection);
}
}
- else if (textFormatter is { VerticalAlignment: VerticalTextAlignment.Middle })
+ else if (textFormatter is { VerticalAlignment: Alignment.Center })
{
return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection);
}
@@ -1475,7 +1478,7 @@ public class TextFormatter
/// Formats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries.
///
/// The number of columns to constrain the text to for word wrapping and clipping.
- /// Specifies how the text will be aligned horizontally.
+ /// Specifies how the text will be aligned horizontally.
///
/// If , the text will be wrapped to new lines no longer than
/// . If , forces text to fit a single line. Line breaks are converted
@@ -1498,7 +1501,7 @@ public class TextFormatter
public static List Format (
string text,
int width,
- TextAlignment talign,
+ Alignment textAlignment,
bool wordWrap,
bool preserveTrailingSpaces = false,
int tabWidth = 0,
@@ -1510,7 +1513,7 @@ public class TextFormatter
return Format (
text,
width,
- talign == TextAlignment.Justified,
+ textAlignment == Alignment.Fill,
wordWrap,
preserveTrailingSpaces,
tabWidth,
@@ -1884,7 +1887,7 @@ public class TextFormatter
return lineIdx;
}
- /// Calculates the rectangle required to hold text, assuming no word wrapping or justification.
+ /// Calculates the rectangle required to hold text, assuming no word wrapping or alignment.
///
/// This API will return incorrect results if the text includes glyphs who's width is dependent on surrounding
/// glyphs (e.g. Arabic).
diff --git a/Terminal.Gui/Text/VerticalTextAlignment.cs b/Terminal.Gui/Text/VerticalTextAlignment.cs
deleted file mode 100644
index ef7788577..000000000
--- a/Terminal.Gui/Text/VerticalTextAlignment.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Terminal.Gui;
-
-/// Vertical text alignment enumeration, controls how text is displayed.
-public enum VerticalTextAlignment
-{
- /// The text will be top-aligned.
- Top,
-
- /// The text will be bottom-aligned.
- Bottom,
-
- /// The text will centered vertically.
- Middle,
-
- ///
- /// The text will be justified (spaces will be added to existing spaces such that the text fills the container
- /// vertically).
- ///
- Justified
-}
\ No newline at end of file
diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs
index 293c27e88..901852e22 100644
--- a/Terminal.Gui/View/Adornment/Border.cs
+++ b/Terminal.Gui/View/Adornment/Border.cs
@@ -196,6 +196,26 @@ public class Border : Adornment
set => _lineStyle = value;
}
+ private bool _showTitle = true;
+
+ ///
+ /// Gets or sets whether the title should be shown. The default is .
+ ///
+ public bool ShowTitle
+ {
+ get => _showTitle;
+ set
+ {
+ if (value == _showTitle)
+ {
+ return;
+ }
+ _showTitle = value;
+
+ Parent?.SetNeedsDisplay ();
+ }
+ }
+
#region Mouse Support
private Color? _savedForeColor;
@@ -358,7 +378,7 @@ public class Border : Adornment
}
}
-#endregion Mouse Support
+ #endregion Mouse Support
///
public override void OnDrawContent (Rectangle viewport)
@@ -394,12 +414,13 @@ public class Border : Adornment
Math.Min (screenBounds.Width - 4, borderBounds.Width - 4)
)
);
+
Parent.TitleTextFormatter.Size = new (maxTitleWidth, 1);
int sideLineLength = borderBounds.Height;
bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };
- if (!string.IsNullOrEmpty (Parent?.Title))
+ if (ShowTitle)
{
if (Thickness.Top == 2)
{
@@ -431,13 +452,13 @@ public class Border : Adornment
}
}
- if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title))
+ if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && ShowTitle && !string.IsNullOrEmpty (Parent?.Title))
{
- var focus = Parent.GetNormalColor();
+ var focus = Parent.GetNormalColor ();
if (Parent.SuperView is { } && Parent.SuperView?.Subviews!.Count (s => s.CanFocus) > 1)
{
// Only use focus color if there are multiple focusable views
- focus = Parent.GetFocusColor() ;
+ focus = Parent.GetFocusColor ();
}
Parent.TitleTextFormatter.Draw (
@@ -450,9 +471,9 @@ public class Border : Adornment
{
LineCanvas lc = Parent?.LineCanvas;
- bool drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height > 1;
+ bool drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height >= 1;
bool drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
- bool drawBottom = Thickness.Bottom > 0 && Frame.Width > 1;
+ bool drawBottom = Thickness.Bottom > 0 && Frame.Width > 1 && Frame.Height > 1;
bool drawRight = Thickness.Right > 0 && (Frame.Height > 1 || Thickness.Top == 0);
Attribute prevAttr = Driver.GetAttribute ();
@@ -470,7 +491,7 @@ public class Border : Adornment
{
// ╔╡Title╞═════╗
// ╔╡╞═════╗
- if (borderBounds.Width < 4 || string.IsNullOrEmpty (Parent?.Title))
+ if (borderBounds.Width < 4 || !ShowTitle || string.IsNullOrEmpty (Parent?.Title))
{
// ╔╡╞╗ should be ╔══╗
lc.AddLine (
@@ -620,7 +641,7 @@ public class Border : Adornment
}
// Redraw title
- if (drawTop && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title))
+ if (drawTop && maxTitleWidth > 0 && ShowTitle)
{
Parent.TitleTextFormatter.Draw (
new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs
index 1fc16c9c2..259eed499 100644
--- a/Terminal.Gui/View/Layout/Dim.cs
+++ b/Terminal.Gui/View/Layout/Dim.cs
@@ -6,8 +6,7 @@ namespace Terminal.Gui;
///
///
/// A Dim object describes the dimensions of a . Dim is the type of the
-/// and properties of . Dim objects enable
-/// Computed Layout (see ) to automatically manage the dimensions of a view.
+/// and properties of .
///
///
/// Integer values are implicitly convertible to an absolute . These objects are created using
@@ -150,7 +149,7 @@ public abstract class Dim
/// Creates a percentage object that is a percentage of the width or height of the SuperView.
/// The percent object.
/// A value between 0 and 100 representing the percentage.
- ///
+ /// the mode. Defaults to .
///
/// This initializes a that will be centered horizontally, is 50% of the way down, is 30% the
/// height,
@@ -187,7 +186,7 @@ public abstract class Dim
/// Gets a dimension that is anchored to a certain point in the layout.
/// This method is typically used internally by the layout system to determine the size of a View.
///
- /// The width of the area where the View is being sized (Superview.ContentSize).
+ /// The width of the area where the View is being sized (Superview.GetContentSize ()).
///
/// An integer representing the calculated dimension. The way this dimension is calculated depends on the specific
/// subclass of Dim that is used. For example, DimAbsolute returns a fixed dimension, DimFactor returns a
diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs
index b8dc4204f..0538c6f08 100644
--- a/Terminal.Gui/View/Layout/DimAuto.cs
+++ b/Terminal.Gui/View/Layout/DimAuto.cs
@@ -60,7 +60,8 @@ public class DimAuto () : Dim
var subviewsSize = 0;
int autoMin = MinimumContentDim?.GetAnchor (superviewContentSize) ?? 0;
-
+ int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? int.MaxValue;
+
if (Style.FastHasFlags (DimAutoStyle.Text))
{
textSize = int.Max (autoMin, dimension == Dimension.Width ? us.TextFormatter.Size.Width : us.TextFormatter.Size.Height);
@@ -68,24 +69,46 @@ public class DimAuto () : Dim
if (Style.FastHasFlags (DimAutoStyle.Content))
{
- if (us._contentSize is { })
+ if (!us.ContentSizeTracksViewport)
{
- subviewsSize = dimension == Dimension.Width ? us.ContentSize.Width : us.ContentSize.Height;
+ // ContentSize was explicitly set. Ignore subviews.
+ subviewsSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height;
}
else
{
+ // ContentSize was NOT explicitly set. Use subviews to determine size.
+
// TODO: This whole body of code is a WIP (for https://github.com/gui-cs/Terminal.Gui/pull/3451).
subviewsSize = 0;
+ List includedSubviews = us.Subviews.ToList();//.Where (v => !v.ExcludeFromLayout).ToList ();
List subviews;
+ #region Not Anchored and Are Not Dependent
+ // Start with subviews that are not anchored to the end, aligned, or dependent on content size
+ // [x] PosAnchorEnd
+ // [x] PosAlign
+ // [ ] PosCenter
+ // [ ] PosPercent
+ // [ ] PosView
+ // [ ] PosFunc
+ // [x] DimFill
+ // [ ] DimPercent
+ // [ ] DimFunc
+ // [ ] DimView
if (dimension == Dimension.Width)
{
- subviews = us.Subviews.Where (v => v.X is not PosAnchorEnd && v.Width is not DimFill).ToList ();
+ subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd
+ && v.X is not PosAlign
+ // && v.X is not PosCenter
+ && v.Width is not DimFill).ToList ();
}
else
{
- subviews = us.Subviews.Where (v => v.Y is not PosAnchorEnd && v.Height is not DimFill).ToList ();
+ subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd
+ && v.Y is not PosAlign
+ // && v.Y is not PosCenter
+ && v.Height is not DimFill).ToList ();
}
for (var i = 0; i < subviews.Count; i++)
@@ -96,17 +119,22 @@ public class DimAuto () : Dim
if (size > subviewsSize)
{
+ // BUGBUG: Should we break here? Or choose min/max?
subviewsSize = size;
}
}
+ #endregion Not Anchored and Are Not Dependent
+ #region Anchored
+ // Now, handle subviews that are anchored to the end
+ // [x] PosAnchorEnd
if (dimension == Dimension.Width)
{
- subviews = us.Subviews.Where (v => v.X is PosAnchorEnd).ToList ();
+ subviews = includedSubviews.Where (v => v.X is PosAnchorEnd).ToList ();
}
else
{
- subviews = us.Subviews.Where (v => v.Y is PosAnchorEnd).ToList ();
+ subviews = includedSubviews.Where (v => v.Y is PosAnchorEnd).ToList ();
}
int maxAnchorEnd = 0;
@@ -117,31 +145,64 @@ public class DimAuto () : Dim
}
subviewsSize += maxAnchorEnd;
+ #endregion Anchored
+ //#region Center
+ //// Now, handle subviews that are Centered
+ //if (dimension == Dimension.Width)
+ //{
+ // subviews = us.Subviews.Where (v => v.X is PosCenter).ToList ();
+ //}
+ //else
+ //{
+ // subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList ();
+ //}
+ //int maxCenter = 0;
+ //for (var i = 0; i < subviews.Count; i++)
+ //{
+ // View v = subviews [i];
+ // maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
+ //}
+
+ //subviewsSize += maxCenter;
+ //#endregion Center
+
+ #region Are Dependent
+ // Now, go back to those that are dependent on content size
+ // [x] DimFill
+ // [ ] DimPercent
if (dimension == Dimension.Width)
{
- subviews = us.Subviews.Where (v => v.Width is DimFill).ToList ();
+ subviews = includedSubviews.Where (v => v.Width is DimFill
+ // || v.X is PosCenter
+ ).ToList ();
}
else
{
- subviews = us.Subviews.Where (v => v.Height is DimFill).ToList ();
+ subviews = includedSubviews.Where (v => v.Height is DimFill
+ //|| v.Y is PosCenter
+ ).ToList ();
}
+ int maxFill = 0;
for (var i = 0; i < subviews.Count; i++)
{
View v = subviews [i];
if (dimension == Dimension.Width)
{
- v.SetRelativeLayout (new Size (autoMin - subviewsSize, 0));
+ v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
}
else
{
- v.SetRelativeLayout (new Size (0, autoMin - subviewsSize));
+ v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
}
+ maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
}
+ subviewsSize += maxFill;
+ #endregion Are Dependent
}
}
@@ -156,14 +217,14 @@ public class DimAuto () : Dim
Thickness thickness = us.GetAdornmentsThickness ();
max += dimension switch
- {
- Dimension.Width => thickness.Horizontal,
- Dimension.Height => thickness.Vertical,
- Dimension.None => 0,
- _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null)
- };
+ {
+ Dimension.Width => thickness.Horizontal,
+ Dimension.Height => thickness.Vertical,
+ Dimension.None => 0,
+ _ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null)
+ };
- return int.Min (max, MaximumContentDim?.GetAnchor (superviewContentSize) ?? max);
+ return int.Min (max, autoMax);
}
internal override bool ReferencesOtherViews ()
diff --git a/Terminal.Gui/View/Layout/DimAutoStyle.cs b/Terminal.Gui/View/Layout/DimAutoStyle.cs
index 85b162569..f350e8045 100644
--- a/Terminal.Gui/View/Layout/DimAutoStyle.cs
+++ b/Terminal.Gui/View/Layout/DimAutoStyle.cs
@@ -10,11 +10,9 @@ namespace Terminal.Gui;
public enum DimAutoStyle
{
///
- /// The dimensions will be computed based on the View's non-Text content.
+ /// The dimensions will be computed based on the View's and/or .
///
- /// If is explicitly set (is not ) then
- ///
- /// will be used to determine the dimension.
+ /// If is , will be used to determine the dimension.
///
///
/// Otherwise, the Subview in with the largest corresponding position plus dimension
@@ -24,7 +22,7 @@ public enum DimAutoStyle
/// The corresponding dimension of the view's will be ignored.
///
///
- Content = 0,
+ Content = 1,
///
///
@@ -33,14 +31,14 @@ public enum DimAutoStyle
/// will be used to determine the dimension.
///
///
- /// The corresponding dimensions of the will be ignored.
+ /// The corresponding dimensions of and/or will be ignored.
///
///
- Text = 1,
+ Text = 2,
///
- /// The dimension will be computed using both the view's and
- /// (whichever is larger).
+ /// The dimension will be computed using the largest of the view's , , and
+ /// corresponding dimension
///
Auto = Content | Text,
}
\ No newline at end of file
diff --git a/Terminal.Gui/View/Layout/DimPercent.cs b/Terminal.Gui/View/Layout/DimPercent.cs
index af849c53f..2ae81302a 100644
--- a/Terminal.Gui/View/Layout/DimPercent.cs
+++ b/Terminal.Gui/View/Layout/DimPercent.cs
@@ -11,7 +11,7 @@ namespace Terminal.Gui;
/// The percentage.
///
/// If the dimension is computed using the View's position ( or
-/// ); otherwise, the dimension is computed using the View's .
+/// ); otherwise, the dimension is computed using the View's .
///
public class DimPercent (int percent, DimPercentMode mode = DimPercentMode.ContentSize) : Dim
{
@@ -32,7 +32,7 @@ public class DimPercent (int percent, DimPercentMode mode = DimPercentMode.Conte
public override string ToString () { return $"Percent({Percent},{Mode})"; }
///
- /// Gets whether the dimension is computed using the View's position or ContentSize.
+ /// Gets whether the dimension is computed using the View's position or GetContentSize ().
///
public DimPercentMode Mode { get; } = mode;
diff --git a/Terminal.Gui/View/Layout/DimPercentMode.cs b/Terminal.Gui/View/Layout/DimPercentMode.cs
index 74d64b77c..60a7da056 100644
--- a/Terminal.Gui/View/Layout/DimPercentMode.cs
+++ b/Terminal.Gui/View/Layout/DimPercentMode.cs
@@ -15,7 +15,7 @@ public enum DimPercentMode
Position = 0,
///
- /// The dimension is computed using the View's .
+ /// The dimension is computed using the View's .
///
ContentSize = 1
}
\ No newline at end of file
diff --git a/Terminal.Gui/View/Layout/LayoutStyle.cs b/Terminal.Gui/View/Layout/LayoutStyle.cs
deleted file mode 100644
index 81883bfcc..000000000
--- a/Terminal.Gui/View/Layout/LayoutStyle.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using Terminal.Gui.Analyzers.Internal.Attributes;
-
-namespace Terminal.Gui;
-
-///
-/// Indicates the LayoutStyle for the .
-///
-/// If Absolute, the , , , and
-/// objects are all absolute values and are not relative. The position and size of the
-/// view is described by .
-///
-///
-/// If Computed, one or more of the , , , or
-/// objects are relative to the and are computed at layout
-/// time.
-///
-///
-[GenerateEnumExtensionMethods]
-public enum LayoutStyle
-{
- ///
- /// Indicates the , , , and
- /// objects are all absolute values and are not relative. The position and size of the view
- /// is described by .
- ///
- Absolute,
-
- ///
- /// Indicates one or more of the , , , or
- ///
- /// objects are relative to the and are computed at layout time. The position and size of
- /// the
- /// view
- /// will be computed based on these objects at layout time. will provide the absolute computed
- /// values.
- ///
- Computed
-}
\ No newline at end of file
diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs
index e27fabb3c..a5cf52249 100644
--- a/Terminal.Gui/View/Layout/Pos.cs
+++ b/Terminal.Gui/View/Layout/Pos.cs
@@ -26,6 +26,14 @@ namespace Terminal.Gui;
///
///
///
+///
+///
+///
+/// Creates a object that aligns a set of views.
+///
+///
+///
+///
///
///
///
@@ -132,6 +140,30 @@ public abstract class Pos
/// The value to convert to the .
public static Pos Absolute (int position) { return new PosAbsolute (position); }
+ ///
+ /// Creates a object that aligns a set of views according to the specified
+ /// and .
+ ///
+ /// The alignment. The default includes .
+ /// The optional alignment modes.
+ ///
+ /// The optional identifier of a set of views that should be aligned together. When only a single
+ /// set of views in a SuperView is aligned, this parameter is optional.
+ ///
+ ///
+ public static Pos Align (Alignment alignment, AlignmentModes modes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems, int groupId = 0)
+ {
+ return new PosAlign
+ {
+ Aligner = new ()
+ {
+ Alignment = alignment,
+ AlignmentModes = modes
+ },
+ GroupId = groupId
+ };
+ }
+
///
/// Creates a object that is anchored to the end (right side or
/// bottom) of the SuperView's Content Area, minus the respective size of the View. This is equivalent to using
@@ -264,12 +296,12 @@ public abstract class Pos
///
/// Gets the starting point of an element based on the size of the parent element (typically
- /// Superview.ContentSize).
+ /// Superview.GetContentSize ()).
/// This method is meant to be overridden by subclasses to provide different ways of calculating the starting point.
/// This method is used
/// internally by the layout system to determine where a View should be positioned.
///
- /// The size of the parent element (typically Superview.ContentSize).
+ /// The size of the parent element (typically Superview.GetContentSize ()).
///
/// An integer representing the calculated position. The way this position is calculated depends on the specific
/// subclass of Pos that is used. For example, PosAbsolute returns a fixed position, PosAnchorEnd returns a
@@ -359,5 +391,4 @@ public abstract class Pos
}
#endregion operators
-
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/View/Layout/PosAlign.cs b/Terminal.Gui/View/Layout/PosAlign.cs
new file mode 100644
index 000000000..31bb81a26
--- /dev/null
+++ b/Terminal.Gui/View/Layout/PosAlign.cs
@@ -0,0 +1,222 @@
+#nullable enable
+
+using System.ComponentModel;
+using System.Drawing;
+
+namespace Terminal.Gui;
+
+///
+/// Enables alignment of a set of views.
+///
+///
+///
+/// Updating the properties of is supported, but will not automatically cause re-layout to
+/// happen.
+/// must be called on the SuperView.
+///
+///
+/// Views that should be aligned together must have a distinct . When only a single
+/// set of views is aligned within a SuperView, setting is optional because it defaults to 0.
+///
+///
+/// The first view added to the Superview with a given is used to determine the alignment of
+/// the group.
+/// The alignment is applied to all views with the same .
+///
+///
+public class PosAlign : Pos
+{
+ ///
+ /// The cached location. Used to store the calculated location to minimize recalculating it.
+ ///
+ private int? _cachedLocation;
+
+ ///
+ /// Gets the identifier of a set of views that should be aligned together. When only a single
+ /// set of views in a SuperView is aligned, setting is not needed because it defaults to 0.
+ ///
+ public int GroupId { get; init; }
+
+ private readonly Aligner? _aligner;
+
+ ///
+ /// Gets the alignment settings.
+ ///
+ public required Aligner Aligner
+ {
+ get => _aligner!;
+ init
+ {
+ if (_aligner is { })
+ {
+ _aligner.PropertyChanged -= Aligner_PropertyChanged;
+ }
+
+ _aligner = value;
+ _aligner.PropertyChanged += Aligner_PropertyChanged;
+ }
+ }
+
+ ///
+ /// Aligns the views in that have the same group ID as .
+ /// Updates each view's cached _location.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void AlignAndUpdateGroup (int groupId, IList views, Dimension dimension, int size)
+ {
+ List dimensionsList = new ();
+
+ // PERF: If this proves a perf issue, consider caching a ref to this list in each item
+ List viewsInGroup = views.Where (
+ v =>
+ {
+ return dimension switch
+ {
+ Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
+ Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
+ _ => false
+ };
+ })
+ .ToList ();
+
+ if (viewsInGroup.Count == 0)
+ {
+ return;
+ }
+
+ // PERF: We iterate over viewsInGroup multiple times here.
+
+ Aligner? firstInGroup = null;
+
+ // Update the dimensionList with the sizes of the views
+ for (var index = 0; index < viewsInGroup.Count; index++)
+ {
+ View view = viewsInGroup [index];
+ PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign;
+
+ if (posAlign is { })
+ {
+ if (index == 0)
+ {
+ firstInGroup = posAlign.Aligner;
+ }
+
+ dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height);
+ }
+ }
+
+ // Update the first item in the group with the new container size.
+ firstInGroup!.ContainerSize = size;
+
+ // Align
+ int [] locations = firstInGroup.Align (dimensionsList.ToArray ());
+
+ // Update the cached location for each item
+ for (var index = 0; index < viewsInGroup.Count; index++)
+ {
+ View view = viewsInGroup [index];
+ PosAlign? align = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign;
+
+ if (align is { })
+ {
+ align._cachedLocation = locations [index];
+ }
+ }
+ }
+
+ private void Aligner_PropertyChanged (object? sender, PropertyChangedEventArgs e) { _cachedLocation = null; }
+
+ ///
+ public override bool Equals (object? other)
+ {
+ return other is PosAlign align
+ && GroupId == align.GroupId
+ && align.Aligner.Alignment == Aligner.Alignment
+ && align.Aligner.AlignmentModes == Aligner.AlignmentModes;
+ }
+
+ ///
+ public override int GetHashCode () { return HashCode.Combine (Aligner, GroupId); }
+
+ ///
+ public override string ToString () { return $"Align(alignment={Aligner.Alignment},modes={Aligner.AlignmentModes},groupId={GroupId})"; }
+
+ internal override int GetAnchor (int width) { return _cachedLocation ?? 0 - width; }
+
+ internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension)
+ {
+ if (_cachedLocation.HasValue && Aligner.ContainerSize == superviewDimension)
+ {
+ return _cachedLocation.Value;
+ }
+
+ if (us?.SuperView is null)
+ {
+ return 0;
+ }
+
+ AlignAndUpdateGroup (GroupId, us.SuperView.Subviews, dimension, superviewDimension);
+
+ if (_cachedLocation.HasValue)
+ {
+ return _cachedLocation.Value;
+ }
+
+ return 0;
+ }
+
+ internal int CalculateMinDimension (int groupId, IList views, Dimension dimension)
+ {
+ List dimensionsList = new ();
+
+ // PERF: If this proves a perf issue, consider caching a ref to this list in each item
+ List viewsInGroup = views.Where (
+ v =>
+ {
+ return dimension switch
+ {
+ Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
+ Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
+ _ => false
+ };
+ })
+ .ToList ();
+
+ if (viewsInGroup.Count == 0)
+ {
+ return 0;
+ }
+
+ // PERF: We iterate over viewsInGroup multiple times here.
+
+ Aligner? firstInGroup = null;
+
+ // Update the dimensionList with the sizes of the views
+ for (var index = 0; index < viewsInGroup.Count; index++)
+ {
+ View view = viewsInGroup [index];
+
+ PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign;
+
+ if (posAlign is { })
+ {
+ if (index == 0)
+ {
+ firstInGroup = posAlign.Aligner;
+ }
+
+ dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height);
+ }
+ }
+
+ // Align
+ var aligner = firstInGroup;
+ aligner.ContainerSize = dimensionsList.Sum();
+ int [] locations = aligner.Align (dimensionsList.ToArray ());
+
+ return locations.Sum ();
+ }
+}
diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs
index 94348fc75..58b0d23ce 100644
--- a/Terminal.Gui/View/Layout/ViewLayout.cs
+++ b/Terminal.Gui/View/Layout/ViewLayout.cs
@@ -1,6 +1,5 @@
#nullable enable
using System.Diagnostics;
-using Microsoft.CodeAnalysis;
namespace Terminal.Gui;
@@ -13,14 +12,13 @@ public partial class View
/// Gets or sets the absolute location and dimension of the view.
///
/// The rectangle describing absolute location and dimension of the view, in coordinates relative to the
- /// 's Content, which is bound by .
+ /// 's Content, which is bound by .
///
///
- /// Frame is relative to the 's Content, which is bound by .
+ /// Frame is relative to the 's Content, which is bound by .
///
/// Setting Frame will set , , , and to the
/// values of the corresponding properties of the parameter.
- /// This causes to be .
///
///
/// Altering the Frame will eventually (when the view hierarchy is next laid out via see
@@ -41,8 +39,7 @@ public partial class View
SetFrame (value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) });
- // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so
- // set all Pos/Dim to Absolute values.
+ // If Frame gets set, set all Pos/Dim to Absolute values.
_x = _frame.X;
_y = _frame.Y;
_width = _frame.Width;
@@ -136,7 +133,7 @@ public partial class View
/// The object representing the X position.
///
///
- /// The position is relative to the 's Content, which is bound by .
+ /// The position is relative to the 's Content, which is bound by .
///
///
/// If set to a relative value (e.g. ) the value is indeterminate until the view has been
@@ -148,8 +145,7 @@ public partial class View
/// and methods to be called.
///
///
- /// Changing this property will cause to be updated. If the new value is not of type
- /// the will change to .
+ /// Changing this property will cause to be updated.
///
/// The default value is Pos.At (0).
///
@@ -175,7 +171,7 @@ public partial class View
/// The object representing the Y position.
///
///
- /// The position is relative to the 's Content, which is bound by .
+ /// The position is relative to the 's Content, which is bound by .
///
///
/// If set to a relative value (e.g. ) the value is indeterminate until the view has been
@@ -187,8 +183,7 @@ public partial class View
/// and methods to be called.
///
///
- /// Changing this property will cause to be updated. If the new value is not of type
- /// the will change to .
+ /// Changing this property will cause to be updated.
///
/// The default value is Pos.At (0).
///
@@ -213,7 +208,7 @@ public partial class View
/// The object representing the height of the view (the number of rows).
///
///
- /// The dimension is relative to the 's Content, which is bound by
+ /// The dimension is relative to the 's Content, which is bound by
/// .
///
///
@@ -226,8 +221,7 @@ public partial class View
/// and methods to be called.
///
///
- /// Changing this property will cause to be updated. If the new value is not of type
- /// the will change to .
+ /// Changing this property will cause to be updated.
///
/// The default value is Dim.Sized (0).
///
@@ -259,7 +253,7 @@ public partial class View
/// The object representing the width of the view (the number of columns).
///
///
- /// The dimension is relative to the 's Content, which is bound by
+ /// The dimension is relative to the 's Content, which is bound by
/// .
///
///
@@ -272,8 +266,7 @@ public partial class View
/// and methods to be called.
///
///
- /// Changing this property will cause to be updated. If the new value is not of type
- /// the will change to .
+ /// Changing this property will cause to be updated.
///
/// The default value is Dim.Sized (0).
///
@@ -303,55 +296,6 @@ public partial class View
#region Layout Engine
-
- // @tig Notes on layout flow. Ignore for now.
- // BeginLayout
- // If !LayoutNeeded return
- // If !SizeNeeded return
- // Call OnLayoutStarted
- // Views and subviews can update things
- //
-
-
- // EndLayout
-
- ///
- /// Controls how the View's is computed during . If the style is
- /// set to , LayoutSubviews does not change the . If the style is
- /// the is updated using the , ,
- /// , and properties.
- ///
- ///
- ///
- /// Setting this property to will cause to determine the
- /// size and position of the view. and will be set to
- /// using .
- ///
- ///
- /// Setting this property to will cause the view to use the
- /// method to size and position of the view. If either of the and
- /// properties are `null` they will be set to using the current value
- /// of . If either of the and properties are `null`
- /// they will be set to using .
- ///
- ///
- /// The layout style.
- public LayoutStyle LayoutStyle
- {
- get
- {
- if (_x is PosAbsolute
- && _y is PosAbsolute
- && _width is DimAbsolute
- && _height is DimAbsolute)
- {
- return LayoutStyle.Absolute;
- }
-
- return LayoutStyle.Computed;
- }
- }
-
#endregion Layout Engine
///
@@ -626,7 +570,7 @@ public partial class View
CheckDimAuto ();
- var contentSize = ContentSize;
+ var contentSize = GetContentSize ();
OnLayoutStarted (new (contentSize));
LayoutAdornments ();
@@ -650,7 +594,7 @@ public partial class View
{
foreach ((View from, View to) in edges)
{
- LayoutSubview (to, from.ContentSize);
+ LayoutSubview (to, from.GetContentSize ());
}
}
@@ -703,8 +647,8 @@ public partial class View
// Determine our container's ContentSize -
// First try SuperView.Viewport, then Application.Top, then Driver.Viewport.
// Finally, if none of those are valid, use int.MaxValue (for Unit tests).
- Size superViewContentSize = SuperView is { IsInitialized: true } ? SuperView.ContentSize :
- Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.ContentSize :
+ Size superViewContentSize = SuperView is { IsInitialized: true } ? SuperView.GetContentSize () :
+ Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.GetContentSize () :
Application.Driver?.Screen.Size ?? new (int.MaxValue, int.MaxValue);
SetTextFormatterSize ();
@@ -746,7 +690,7 @@ public partial class View
///
/// Adjusts given the SuperView's ContentSize (nominally the same as
- /// this.SuperView.ContentSize)
+ /// this.SuperView.GetContentSize ())
/// and the position (, ) and dimension (, and
/// ).
///
@@ -758,7 +702,7 @@ public partial class View
///
///
///
- /// The size of the SuperView's content (nominally the same as this.SuperView.ContentSize).
+ /// The size of the SuperView's content (nominally the same as this.SuperView.GetContentSize ()).
///
internal void SetRelativeLayout (Size superviewContentSize)
{
@@ -796,8 +740,7 @@ public partial class View
if (Frame != newFrame)
{
- // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
- // the view LayoutStyle.Absolute.
+ // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height
SetFrame (newFrame);
if (_x is PosAbsolute)
@@ -835,12 +778,6 @@ public partial class View
foreach (View? v in from.InternalSubviews)
{
nNodes.Add (v);
-
- if (v.LayoutStyle != LayoutStyle.Computed)
- {
- continue;
- }
-
CollectPos (v.X, v, ref nNodes, ref nEdges);
CollectPos (v.Y, v, ref nNodes, ref nEdges);
CollectDim (v.Width, v, ref nNodes, ref nEdges);
@@ -1049,13 +986,13 @@ public partial class View
// Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions.
foreach (View view in Subviews)
{
- if (widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Content) && _contentSize is null)
+ if (widthAuto is { } && widthAuto.Style.FastHasFlags (DimAutoStyle.Content) && ContentSizeTracksViewport)
{
ThrowInvalid (view, view.Width, nameof (view.Width));
ThrowInvalid (view, view.X, nameof (view.X));
}
- if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Content) && _contentSize is null)
+ if (heightAuto is { } && heightAuto.Style.FastHasFlags (DimAutoStyle.Content) && ContentSizeTracksViewport)
{
ThrowInvalid (view, view.Height, nameof (view.Height));
ThrowInvalid (view, view.Y, nameof (view.Y));
diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs
index 1632eca2f..441b0a3ac 100644
--- a/Terminal.Gui/View/View.cs
+++ b/Terminal.Gui/View/View.cs
@@ -43,13 +43,6 @@ namespace Terminal.Gui;
/// more subviews, can respond to user input and render themselves on the screen.
///
///
-/// View supports two layout styles: or .
-/// The style is determined by the values of , , , and
-/// . If any of these is set to non-absolute or object,
-/// then the layout style is . Otherwise it is
-/// .
-///
-///
/// To create a View using Absolute layout, call a constructor that takes a Rect parameter to specify the
/// absolute position and size or simply set ). To create a View using Computed layout use
/// a constructor that does not take a Rect parameter and set the X, Y, Width and Height properties on the view to
@@ -79,9 +72,7 @@ namespace Terminal.Gui;
/// To flag the entire view for redraw call .
///
///
-/// The method is invoked when the size or layout of a view has changed. The default
-/// processing system will keep the size and dimensions for views that use the ,
-/// and will recompute the Adornments for the views that use .
+/// The method is invoked when the size or layout of a view has changed.
///
///
/// Views have a property that defines the default colors that subviews should use for
@@ -127,38 +118,25 @@ public partial class View : Responder, ISupportInitializeNotification
///
///
/// Use , , , and properties to dynamically
- /// control the size and location of the view. The will be created using
- /// coordinates. The initial size ( ) will be adjusted
- /// to fit the contents of , including newlines ('\n') for multiple lines.
- ///
- /// If is greater than one, word wrapping is provided.
- ///
- /// This constructor initialize a View with a of .
- /// Use , , , and properties to dynamically
- /// control the size and location of the view, changing it to .
+ /// control the size and location of the view.
///
///
public View ()
{
- CreateAdornments ();
-
- HotKeySpecifier = (Rune)'_';
- TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged;
-
- TextDirection = TextDirection.LeftRight_TopBottom;
- Text = string.Empty;
+ SetupAdornments ();
+ SetupKeyboard ();
+ //SetupMouse ();
+ SetupText ();
CanFocus = false;
TabIndex = -1;
TabStop = false;
-
- AddCommands ();
}
///
/// Event called only once when the is being initialized for the first time. Allows
- /// configurations and assignments to be performed before the being shown. This derived from
- /// to allow notify all the views that are being initialized.
+ /// configurations and assignments to be performed before the being shown.
+ /// View implements to allow for more sophisticated initialization.
///
public event EventHandler Initialized;
@@ -525,6 +503,7 @@ public partial class View : Responder, ISupportInitializeNotification
{
LineCanvas.Dispose ();
+ DisposeKeyboard ();
DisposeAdornments ();
for (int i = InternalSubviews.Count - 1; i >= 0; i--)
diff --git a/Terminal.Gui/View/ViewAdornments.cs b/Terminal.Gui/View/ViewAdornments.cs
index 54b4609b1..0ebf5499f 100644
--- a/Terminal.Gui/View/ViewAdornments.cs
+++ b/Terminal.Gui/View/ViewAdornments.cs
@@ -2,7 +2,10 @@
public partial class View
{
- private void CreateAdornments ()
+ ///
+ /// Initializes the Adornments of the View. Called by the constructor.
+ ///
+ private void SetupAdornments ()
{
//// TODO: Move this to Adornment as a static factory method
if (this is not Adornment)
diff --git a/Terminal.Gui/View/ViewContent.cs b/Terminal.Gui/View/ViewContent.cs
index 7fa48ed38..08443a851 100644
--- a/Terminal.Gui/View/ViewContent.cs
+++ b/Terminal.Gui/View/ViewContent.cs
@@ -1,6 +1,4 @@
-using System.Diagnostics;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
public partial class View
{
@@ -13,33 +11,31 @@ public partial class View
///
///
///
- /// By default, the content size is set to .
- ///
- ///
- ///
- ///
- /// If , and the View has no visible subviews, will track the size of .
- ///
- ///
- /// If , and the View has visible subviews, will track the maximum position plus size of any
- /// visible Subviews
- /// and Viewport.Location will track the minimum position and size of any visible Subviews.
- ///
- ///
- /// If not , is set to the passed value and describes the portion of the content currently visible
- /// to the user. This enables virtual scrolling.
- ///
- ///
- /// If not , is set to the passed value and the behavior of will be to use the ContentSize
- /// to determine the size of the view.
- ///
- ///
/// Negative sizes are not supported.
///
- ///
+ ///
+ /// If not explicitly set, and the View has no visible subviews, will return the
+ /// size of
+ /// .
+ ///
+ ///
+ /// If not explicitly set, and the View has visible subviews, will return the
+ /// maximum
+ /// position + dimension of the Subviews, supporting with the
+ /// flag set.
+ ///
+ ///
+ /// If set describes the portion of the content currently visible to the user. This enables
+ /// virtual scrolling.
+ ///
+ ///
+ /// If set the behavior of will be to use the ContentSize to determine the size
+ /// of the view.
+ ///
+ ///
public void SetContentSize (Size? contentSize)
{
- if (ContentSize.Width < 0 || ContentSize.Height < 0)
+ if (contentSize is { } && (contentSize.Value.Width < 0 || contentSize.Value.Height < 0))
{
throw new ArgumentException (@"ContentSize cannot be negative.", nameof (contentSize));
}
@@ -56,19 +52,86 @@ public partial class View
///
/// Gets the size of the View's content.
///
- ///
+ /// a>
///
- /// Use to change to change the content size.
- ///
- ///
- /// If the content size has not been explicitly set with , the value tracks
+ /// If the content size was not explicitly set by , and the View has no visible subviews, will return the
+ /// size of
/// .
///
+ ///
+ /// If the content size was not explicitly set by , and the View has visible subviews, will return the
+ /// maximum
+ /// position + dimension of the Subviews, supporting with the
+ /// flag set.
+ ///
+ ///
+ /// If set describes the portion of the content currently visible to the user. This enables
+ /// virtual scrolling.
+ ///
+ ///
+ /// If set the behavior of will be to use the ContentSize to determine the size
+ /// of the view.
+ ///
///
- public Size ContentSize => _contentSize ?? Viewport.Size;
+ ///
+ /// If the content size was not explicitly set by , will
+ /// return the size of the and will be .
+ ///
+ public Size GetContentSize () { return _contentSize ?? Viewport.Size; }
///
- /// Called when has changed.
+ /// Gets or sets a value indicating whether the view's content size tracks the 's
+ /// size or not.
+ ///
+ ///
+ ///
+ ///
+ /// ValueResult
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// will return the 's size. Content scrolling
+ /// will be
+ /// disabled.
+ ///
+ ///
+ /// The behavior of will be to use position and size of the Subviews
+ /// to
+ /// determine the size of the view, ignoring .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The return value of is independent of and
+ /// describes the portion of the content currently visible to the user enabling content scrolling.
+ ///
+ ///
+ /// The behavior of will be to use
+ /// to
+ /// determine the
+ /// size of the view, ignoring the position and size of the Subviews.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool ContentSizeTracksViewport
+ {
+ get => _contentSize is null;
+ set => _contentSize = value ? null : _contentSize;
+ }
+
+ ///
+ /// Called when has changed.
///
///
///
@@ -79,6 +142,7 @@ public partial class View
if (e.Cancel != true)
{
OnResizeNeeded ();
+
//SetNeedsLayout ();
//SetNeedsDisplay ();
}
@@ -87,7 +151,7 @@ public partial class View
}
///
- /// Event raised when the changes.
+ /// Event raised when the changes.
///
public event EventHandler ContentSizeChanged;
@@ -155,37 +219,35 @@ public partial class View
///
/// The location of the viewport into the view's content (0,0) is the top-left corner of the content. The Content
/// area's size
- /// is .
+ /// is .
///
private Point _viewportLocation;
///
/// Gets or sets the rectangle describing the portion of the View's content that is visible to the user.
/// The viewport Location is relative to the top-left corner of the inner rectangle of .
- /// If the viewport Size is the same as , or is
+ /// If the viewport Size is the same as , or is
/// the Location will be 0, 0.
///
///
/// The rectangle describing the location and size of the viewport into the View's virtual content, described by
- /// .
+ /// .
///
///
///
/// Positive values for the location indicate the visible area is offset into (down-and-right) the View's virtual
- /// . This enables scrolling down and to the right (e.g. in a .
+ /// . This enables scrolling down and to the right (e.g. in a
+ /// .
///
///
/// Negative values for the location indicate the visible area is offset above (up-and-left) the View's virtual
- /// . This enables scrolling up and to the left (e.g. in an image viewer that supports zoom
+ /// . This enables scrolling up and to the left (e.g. in an image viewer that
+ /// supports
+ /// zoom
/// where the image stays centered).
///
///
- /// The property controls how scrolling is handled.
- ///
- ///
- /// If is the value of Viewport is indeterminate until
- /// the view has been initialized ( is true) and has been
- /// called.
+ /// The property controls how scrolling is handled.
///
///
/// Updates to the Viewport Size updates , and has the same impact as updating the
@@ -207,6 +269,7 @@ public partial class View
}
Thickness thickness = GetAdornmentsThickness ();
+
return new (
_viewportLocation,
new (
@@ -239,6 +302,7 @@ public partial class View
}
OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport));
+
return;
}
@@ -254,9 +318,9 @@ public partial class View
{
if (!ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth))
{
- if (newViewport.X >= ContentSize.Width)
+ if (newViewport.X >= GetContentSize ().Width)
{
- newViewport.X = ContentSize.Width - 1;
+ newViewport.X = GetContentSize ().Width - 1;
}
}
@@ -271,9 +335,9 @@ public partial class View
if (!ViewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight))
{
- if (newViewport.Y >= ContentSize.Height)
+ if (newViewport.Y >= GetContentSize ().Height)
{
- newViewport.Y = ContentSize.Height - 1;
+ newViewport.Y = GetContentSize ().Height - 1;
}
}
@@ -289,7 +353,8 @@ public partial class View
}
///
- /// Fired when the changes. This event is fired after the has been updated.
+ /// Fired when the changes. This event is fired after the has been
+ /// updated.
///
[CanBeNull]
public event EventHandler ViewportChanged;
@@ -298,10 +363,7 @@ public partial class View
/// Called when the changes. Invokes the event.
///
///
- protected virtual void OnViewportChanged (DrawEventArgs e)
- {
- ViewportChanged?.Invoke (this, e);
- }
+ protected virtual void OnViewportChanged (DrawEventArgs e) { ViewportChanged?.Invoke (this, e); }
///
/// Converts a -relative location and size to a screen-relative location and size.
@@ -311,10 +373,7 @@ public partial class View
///
/// Viewport-relative location and size.
/// Screen-relative location and size.
- public Rectangle ViewportToScreen (in Rectangle viewport)
- {
- return viewport with { Location = ViewportToScreen (viewport.Location) };
- }
+ public Rectangle ViewportToScreen (in Rectangle viewport) { return viewport with { Location = ViewportToScreen (viewport.Location) }; }
///
/// Converts a -relative location to a screen-relative location.
@@ -367,7 +426,7 @@ public partial class View
/// if the was changed.
public bool? ScrollVertical (int rows)
{
- if (ContentSize == Size.Empty || ContentSize == Viewport.Size)
+ if (GetContentSize () == Size.Empty || GetContentSize () == Viewport.Size)
{
return false;
}
@@ -388,7 +447,7 @@ public partial class View
/// if the was changed.
public bool? ScrollHorizontal (int cols)
{
- if (ContentSize == Size.Empty || ContentSize == Viewport.Size)
+ if (GetContentSize () == Size.Empty || GetContentSize () == Viewport.Size)
{
return false;
}
diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs
index 95232fa8c..74beffd3f 100644
--- a/Terminal.Gui/View/ViewDrawing.cs
+++ b/Terminal.Gui/View/ViewDrawing.cs
@@ -106,7 +106,7 @@ public partial class View
if (ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly))
{
- Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), ContentSize));
+ Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
toClear = Rectangle.Intersect (toClear, visibleContent);
}
@@ -172,7 +172,7 @@ public partial class View
if (ViewportSettings.HasFlag (ViewportSettings.ClipContentOnly))
{
// Clamp the Clip to the just content area that is within the viewport
- Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), ContentSize));
+ Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
clip = Rectangle.Intersect (clip, visibleContent);
}
@@ -236,6 +236,16 @@ public partial class View
OnRenderLineCanvas ();
+ // TODO: This is a hack to force the border subviews to draw.
+ if (Border?.Subviews is { })
+ {
+ foreach (View view in Border.Subviews)
+ {
+ view.SetNeedsDisplay ();
+ view.Draw ();
+ }
+ }
+
// Invoke DrawContentCompleteEvent
OnDrawContentComplete (Viewport);
@@ -329,7 +339,7 @@ public partial class View
public virtual Attribute GetFocusColor ()
{
ColorScheme cs = ColorScheme;
- if (ColorScheme is null)
+ if (cs is null)
{
cs = new ();
}
@@ -347,7 +357,7 @@ public partial class View
{
ColorScheme cs = ColorScheme;
- if (ColorScheme is null)
+ if (cs is null)
{
cs = new ();
}
@@ -365,7 +375,7 @@ public partial class View
{
ColorScheme cs = ColorScheme;
- if (ColorScheme is null)
+ if (cs is null)
{
cs = new ();
}
@@ -435,12 +445,12 @@ public partial class View
///
///
/// The Location and Size indicate what part of the View's content, defined
- /// by , is visible and should be drawn. The coordinates taken by and
+ /// by , is visible and should be drawn. The coordinates taken by and
/// are relative to , thus if ViewPort.Location.Y is 5
/// the 6th row of the content should be drawn using MoveTo (x, 5).
///
///
- /// If is larger than ViewPort.Size drawing code should use
+ /// If is larger than ViewPort.Size drawing code should use
/// to constrain drawing for better performance.
///
///
@@ -475,7 +485,7 @@ public partial class View
// This should NOT clear
// TODO: If the output is not in the Viewport, do nothing
- var drawRect = new Rectangle (ContentToScreen (Point.Empty), ContentSize);
+ var drawRect = new Rectangle (ContentToScreen (Point.Empty), GetContentSize ());
TextFormatter?.Draw (
drawRect,
@@ -584,7 +594,7 @@ public partial class View
///
///
/// The location of is relative to the View's content, bound by Size.Empty and
- /// .
+ /// .
///
///
/// If the view has not been initialized ( is ), the area to be
diff --git a/Terminal.Gui/View/ViewKeyboard.cs b/Terminal.Gui/View/ViewKeyboard.cs
index 687491997..7863c6799 100644
--- a/Terminal.Gui/View/ViewKeyboard.cs
+++ b/Terminal.Gui/View/ViewKeyboard.cs
@@ -4,8 +4,15 @@ namespace Terminal.Gui;
public partial class View
{
- private void AddCommands ()
+ ///
+ /// Helper to configure all things keyboard related for a View. Called from the View constructor.
+ ///
+ private void SetupKeyboard ()
{
+ KeyBindings = new (this);
+ HotKeySpecifier = (Rune)'_';
+ TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged;
+
// By default, the HotKey command sets the focus
AddCommand (Command.HotKey, OnHotKey);
@@ -13,6 +20,15 @@ public partial class View
AddCommand (Command.Accept, OnAccept);
}
+ ///
+ /// Helper to dispose all things keyboard related for a View. Called from the View Dispose method.
+ ///
+ private void DisposeKeyboard ()
+ {
+ TitleTextFormatter.HotKeyChanged -= TitleTextFormatter_HotKeyChanged;
+ KeyBindings.Clear ();
+ }
+
#region HotKey Support
///
@@ -113,9 +129,10 @@ public partial class View
///
/// The HotKey is replacing. Key bindings for this key will be removed.
/// The new HotKey. If bindings will be removed.
+ /// Arbitrary context that can be associated with this key binding.
/// if the HotKey bindings were added.
///
- public virtual bool AddKeyBindingsForHotKey (Key prevHotKey, Key hotKey)
+ public virtual bool AddKeyBindingsForHotKey (Key prevHotKey, Key hotKey, [CanBeNull] object context = null)
{
if (_hotKey == hotKey)
{
@@ -178,15 +195,16 @@ public partial class View
// Add the new
if (newKey != Key.Empty)
{
+ KeyBinding keyBinding = new ([Command.HotKey], KeyBindingScope.HotKey, context);
// Add the base and Alt key
- KeyBindings.Add (newKey, KeyBindingScope.HotKey, Command.HotKey);
- KeyBindings.Add (newKey.WithAlt, KeyBindingScope.HotKey, Command.HotKey);
+ KeyBindings.Add (newKey, keyBinding);
+ KeyBindings.Add (newKey.WithAlt, keyBinding);
// If the Key is A..Z, add ShiftMask and AltMask | ShiftMask
if (newKey.IsKeyCodeAtoZ)
{
- KeyBindings.Add (newKey.WithShift, KeyBindingScope.HotKey, Command.HotKey);
- KeyBindings.Add (newKey.WithShift.WithAlt, KeyBindingScope.HotKey, Command.HotKey);
+ KeyBindings.Add (newKey.WithShift, keyBinding);
+ KeyBindings.Add (newKey.WithShift.WithAlt, keyBinding);
}
}
@@ -601,9 +619,9 @@ public partial class View
#region Key Bindings
/// Gets the key bindings for this view.
- public KeyBindings KeyBindings { get; } = new ();
+ public KeyBindings KeyBindings { get; internal set; }
- private Dictionary> CommandImplementations { get; } = new ();
+ private Dictionary> CommandImplementations { get; } = new ();
///
/// Low-level API called when a user presses a key; invokes any key bindings set on the view. This is called
@@ -646,17 +664,17 @@ public partial class View
return true;
}
- if (Margin is {} && ProcessAdornmentKeyBindings (Margin, keyEvent, ref handled))
+ if (Margin is { } && ProcessAdornmentKeyBindings (Margin, keyEvent, ref handled))
{
return true;
}
- if (Padding is {} && ProcessAdornmentKeyBindings (Padding, keyEvent, ref handled))
+ if (Padding is { } && ProcessAdornmentKeyBindings (Padding, keyEvent, ref handled))
{
return true;
}
- if (Border is {} && ProcessAdornmentKeyBindings (Border, keyEvent, ref handled))
+ if (Border is { } && ProcessAdornmentKeyBindings (Border, keyEvent, ref handled))
{
return true;
}
@@ -739,7 +757,7 @@ public partial class View
}
// each command has its own return value
- bool? thisReturn = InvokeCommand (command);
+ bool? thisReturn = InvokeCommand (command, key, binding);
// if we haven't got anything yet, the current command result should be used
toReturn ??= thisReturn;
@@ -758,12 +776,13 @@ public partial class View
/// Invokes the specified commands.
///
///
+ /// The key that caused the commands to be invoked, if any.
///
/// if no command was found.
/// if the command was invoked and it handled the command.
/// if the command was invoked and it did not handle the command.
///
- public bool? InvokeCommands (Command [] commands)
+ public bool? InvokeCommands (Command [] commands, [CanBeNull] Key key = null, [CanBeNull] KeyBinding? keyBinding = null)
{
bool? toReturn = null;
@@ -775,7 +794,7 @@ public partial class View
}
// each command has its own return value
- bool? thisReturn = InvokeCommand (command);
+ bool? thisReturn = InvokeCommand (command, key, keyBinding);
// if we haven't got anything yet, the current command result should be used
toReturn ??= thisReturn;
@@ -791,42 +810,68 @@ public partial class View
}
/// Invokes the specified command.
- ///
+ /// The command to invoke.
+ /// The key that caused the command to be invoked, if any.
+ ///
///
- /// if no command was found. if the command was invoked and it
- /// handled the command. if the command was invoked and it did not handle the command.
+ /// if no command was found. if the command was invoked, and it
+ /// handled the command. if the command was invoked, and it did not handle the command.
///
- public bool? InvokeCommand (Command command)
+ public bool? InvokeCommand (Command command, [CanBeNull] Key key = null, [CanBeNull] KeyBinding? keyBinding = null)
{
- if (!CommandImplementations.ContainsKey (command))
+ if (CommandImplementations.TryGetValue (command, out Func implementation))
{
- return null;
+ var context = new CommandContext (command, key, keyBinding); // Create the context here
+ return implementation (context);
}
- return CommandImplementations [command] ();
+ return null;
}
///
///
/// Sets the function that will be invoked for a . Views should call
- /// for each command they support.
+ /// AddCommand for each command they support.
///
///
- /// If has already been called for will
+ /// If AddCommand has already been called for will
/// replace the old one.
///
///
+ ///
+ ///
+ /// This version of AddCommand is for commands that require . Use
+ /// in cases where the command does not require a .
+ ///
+ ///
+ /// The command.
+ /// The function.
+ protected void AddCommand (Command command, Func f)
+ {
+ CommandImplementations [command] = f;
+ }
+
+ ///
+ ///
+ /// Sets the function that will be invoked for a . Views should call
+ /// AddCommand for each command they support.
+ ///
+ ///
+ /// If AddCommand has already been called for will
+ /// replace the old one.
+ ///
+ ///
+ ///
+ ///
+ /// This version of AddCommand is for commands that do not require a .
+ /// If the command requires context, use
+ ///
+ ///
/// The command.
/// The function.
protected void AddCommand (Command command, Func f)
{
- // if there is already an implementation of this command
- // replace that implementation
- // else record how to perform the action (this should be the normal case)
- if (CommandImplementations is { })
- {
- CommandImplementations [command] = f;
- }
+ CommandImplementations [command] = ctx => f (); ;
}
/// Returns all commands that are supported by this .
diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs
index bf743efe7..f9352ab7d 100644
--- a/Terminal.Gui/View/ViewMouse.cs
+++ b/Terminal.Gui/View/ViewMouse.cs
@@ -447,6 +447,10 @@ public partial class View
internal bool SetHighlight (HighlightStyle style)
{
// TODO: Make the highlight colors configurable
+ if (!CanFocus)
+ {
+ return false;
+ }
// Enable override via virtual method and/or event
if (OnHighlight (style) == true)
diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs
index 2ee3a51a0..12def9323 100644
--- a/Terminal.Gui/View/ViewText.cs
+++ b/Terminal.Gui/View/ViewText.cs
@@ -4,6 +4,15 @@ namespace Terminal.Gui;
public partial class View
{
+ ///
+ /// Initializes the Text of the View. Called by the constructor.
+ ///
+ private void SetupText ()
+ {
+ Text = string.Empty;
+ TextDirection = TextDirection.LeftRight_TopBottom;
+ }
+
private string _text;
///
@@ -37,11 +46,11 @@ public partial class View
/// to and .
///
///
- /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height
+ /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height
/// is 1, the text will be clipped.
///
/// If or are using ,
- /// the will be adjusted to fit the text.
+ /// the will be adjusted to fit the text.
/// When the text changes, the is fired.
///
public virtual string Text
@@ -84,10 +93,10 @@ public partial class View
/// redisplay the .
///
///
- /// or are using , the will be adjusted to fit the text.
+ /// or are using , the will be adjusted to fit the text.
///
/// The text alignment.
- public virtual TextAlignment TextAlignment
+ public virtual Alignment TextAlignment
{
get => TextFormatter.Alignment;
set
@@ -103,9 +112,9 @@ public partial class View
/// .
///
///
- /// or are using , the will be adjusted to fit the text.
+ /// or are using , the will be adjusted to fit the text.
///
- /// The text alignment.
+ /// The text direction.
public virtual TextDirection TextDirection
{
get => TextFormatter.Direction;
@@ -127,10 +136,10 @@ public partial class View
/// the .
///
///
- /// or are using , the will be adjusted to fit the text.
+ /// or are using , the will be adjusted to fit the text.
///
- /// The text alignment.
- public virtual VerticalTextAlignment VerticalTextAlignment
+ /// The vertical text alignment.
+ public virtual Alignment VerticalTextAlignment
{
get => TextFormatter.VerticalAlignment;
set
@@ -179,8 +188,8 @@ public partial class View
// We need to ensure TextFormatter is accurate by calling it here.
UpdateTextFormatterText ();
- // Default is to use ContentSize.
- var size = ContentSize;
+ // Default is to use GetContentSize ().
+ var size = GetContentSize ();
// TODO: This is a hack. Figure out how to move this into DimDimAuto
// Use _width & _height instead of Width & Height to avoid debug spew
@@ -193,12 +202,12 @@ public partial class View
if (widthAuto is null || !widthAuto.Style.FastHasFlags (DimAutoStyle.Text))
{
- size.Width = ContentSize.Width;
+ size.Width = GetContentSize ().Width;
}
if (heightAuto is null || !heightAuto.Style.FastHasFlags (DimAutoStyle.Text))
{
- size.Height = ContentSize.Height;
+ size.Height = GetContentSize ().Height;
}
}
diff --git a/Terminal.Gui/View/ViewportSettings.cs b/Terminal.Gui/View/ViewportSettings.cs
index 443d1b0ca..f0e2af75e 100644
--- a/Terminal.Gui/View/ViewportSettings.cs
+++ b/Terminal.Gui/View/ViewportSettings.cs
@@ -47,13 +47,13 @@ public enum ViewportSettings
AllowNegativeLocation = AllowNegativeX | AllowNegativeY,
///
- /// If set, .X can be set values greater than
+ /// If set, .X can be set values greater than
/// .Width enabling scrolling beyond the right
/// of the content area.
///
///
///
- /// When not set, .X is constrained to
+ /// When not set, .X is constrained to
/// .Width - 1.
/// This means the last column of the content will remain visible even if there is an attempt to scroll the
/// Viewport past the last column.
@@ -65,13 +65,13 @@ public enum ViewportSettings
AllowXGreaterThanContentWidth = 4,
///
- /// If set, .Y can be set values greater than
+ /// If set, .Y can be set values greater than
/// .Height enabling scrolling beyond the right
/// of the content area.
///
///
///
- /// When not set, .Y is constrained to
+ /// When not set, .Y is constrained to
/// .Height - 1.
/// This means the last row of the content will remain visible even if there is an attempt to scroll the Viewport
/// past the last row.
@@ -83,13 +83,13 @@ public enum ViewportSettings
AllowYGreaterThanContentHeight = 8,
///
- /// If set, .Size can be set values greater than
+ /// If set, .Size can be set values greater than
/// enabling scrolling beyond the bottom-right
/// of the content area.
///
///
///
- /// When not set, is constrained to -1.
+ /// When not set, is constrained to -1.
/// This means the last column and row of the content will remain visible even if there is an attempt to
/// scroll the Viewport past the last column or row.
///
diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs
index 52c72ab5f..2d922a79f 100644
--- a/Terminal.Gui/Views/Button.cs
+++ b/Terminal.Gui/Views/Button.cs
@@ -33,12 +33,11 @@ public class Button : View
private readonly Rune _rightDefault;
private bool _isDefault;
- /// Initializes a new instance of using layout.
- /// The width of the is computed based on the text length. The height will always be 1.
+ /// Initializes a new instance of .
public Button ()
{
- TextAlignment = TextAlignment.Centered;
- VerticalTextAlignment = VerticalTextAlignment.Middle;
+ TextAlignment = Alignment.Center;
+ VerticalTextAlignment = Alignment.Center;
_leftBracket = Glyphs.LeftBracket;
_rightBracket = Glyphs.RightBracket;
diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs
index 5971c02ed..471ab5203 100644
--- a/Terminal.Gui/Views/CheckBox.cs
+++ b/Terminal.Gui/Views/CheckBox.cs
@@ -11,8 +11,7 @@ public class CheckBox : View
private bool? _checked = false;
///
- /// Initializes a new instance of based on the given text, using
- /// layout.
+ /// Initializes a new instance of .
///
public CheckBox ()
{
@@ -155,13 +154,13 @@ public class CheckBox : View
{
switch (TextAlignment)
{
- case TextAlignment.Left:
- case TextAlignment.Centered:
- case TextAlignment.Justified:
+ case Alignment.Start:
+ case Alignment.Center:
+ case Alignment.Fill:
TextFormatter.Text = $"{GetCheckedState ()} {Text}";
break;
- case TextAlignment.Right:
+ case Alignment.End:
TextFormatter.Text = $"{Text} {GetCheckedState ()}";
break;
diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs
index c61fdc5f3..addf7a1a4 100644
--- a/Terminal.Gui/Views/ColorPicker.cs
+++ b/Terminal.Gui/Views/ColorPicker.cs
@@ -39,7 +39,7 @@ public class ColorPicker : View
Width = Dim.Auto (minimumContentDim: _boxWidth * _cols);
Height = Dim.Auto (minimumContentDim: _boxHeight * _rows);
- SetContentSize(new (_boxWidth * _cols, _boxHeight * _rows));
+ SetContentSize (new (_boxWidth * _cols, _boxHeight * _rows));
MouseClick += ColorPicker_MouseClick;
}
@@ -178,9 +178,9 @@ public class ColorPicker : View
Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
var colorIndex = 0;
- for (var y = 0; y < Math.Max(2, viewport.Height / BoxHeight); y++)
+ for (var y = 0; y < Math.Max (2, viewport.Height / BoxHeight); y++)
{
- for (var x = 0; x < Math.Max(8, viewport.Width / BoxWidth); x++)
+ for (var x = 0; x < Math.Max (8, viewport.Width / BoxWidth); x++)
{
int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs
index 3eb630e64..36af3853e 100644
--- a/Terminal.Gui/Views/ComboBox.cs
+++ b/Terminal.Gui/Views/ComboBox.cs
@@ -5,7 +5,7 @@
// Ross Ferguson (ross.c.ferguson@btinternet.com)
//
-using System.Collections;
+using System.Collections.ObjectModel;
using System.ComponentModel;
namespace Terminal.Gui;
@@ -16,7 +16,7 @@ public class ComboBox : View
private readonly ComboListView _listview;
private readonly int _minimumHeight = 2;
private readonly TextField _search;
- private readonly IList _searchset = new List
///
/// By default uses to render the items of any
-/// object (e.g. arrays, , and other collections). Alternatively, an
+/// object (e.g. arrays, , and other collections). Alternatively, an
/// object that implements can be provided giving full control of what is rendered.
///
///
@@ -258,12 +265,18 @@ public class ListView : View
set
{
if (_source == value)
-
{
return;
}
+
+ _source?.Dispose ();
_source = value;
+ if (_source is { })
+ {
+ _source.CollectionChanged += Source_CollectionChanged;
+ }
+
SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
if (IsInitialized)
{
@@ -277,6 +290,20 @@ public class ListView : View
}
}
+ private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+ {
+ SetContentSize (new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width));
+
+ if (Source is { Count: > 0 } && _selected > Source.Count - 1)
+ {
+ SelectedItem = Source.Count - 1;
+ }
+
+ SetNeedsDisplay ();
+
+ OnCollectionChanged (e);
+ }
+
/// Gets or sets the index of the item that will appear at the top of the .
///
/// This a helper property for accessing listView.Viewport.Y.
@@ -501,7 +528,12 @@ public class ListView : View
if (Viewport.Y + _selected > Viewport.Height - 1)
{
- Viewport = Viewport with { Y = _selected };
+ Viewport = Viewport with
+ {
+ Y = _selected < Viewport.Height - 1
+ ? Math.Max (Viewport.Height - _selected + 1, 0)
+ : Math.Max (_selected - Viewport.Height + 1, 0)
+ };
}
OnSelectedChanged ();
@@ -796,21 +828,26 @@ public class ListView : View
/// This event is raised when the selected item in the has changed.
public event EventHandler SelectedItemChanged;
+ ///
+ /// Event to raise when an item is added, removed, or moved, or the entire list is refreshed.
+ ///
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
/// Sets the source of the to an .
/// An object implementing the IList interface.
///
- /// Use the property to set a new source and use custome
+ /// Use the property to set a new source and use custom
/// rendering.
///
- public void SetSource (IList source)
+ public void SetSource (ObservableCollection source)
{
- if (source is null && (Source is null || !(Source is ListWrapper)))
+ if (source is null && Source is not ListWrapper)
{
Source = null;
}
else
{
- Source = new ListWrapper (source);
+ Source = new ListWrapper (source);
}
}
@@ -820,18 +857,18 @@ public class ListView : View
/// Use the property to set a new source and use custom
/// rendering.
///
- public Task SetSourceAsync (IList source)
+ public Task SetSourceAsync (ObservableCollection source)
{
return Task.Factory.StartNew (
() =>
{
- if (source is null && (Source is null || !(Source is ListWrapper)))
+ if (source is null && (Source is null || !(Source is ListWrapper)))
{
Source = null;
}
else
{
- Source = new ListWrapper (source);
+ Source = new ListWrapper (source);
}
return source;
@@ -843,35 +880,74 @@ public class ListView : View
}
private void ListView_LayoutStarted (object sender, LayoutEventArgs e) { EnsureSelectedItemVisible (); }
+ ///
+ /// Call the event to raises the .
+ ///
+ ///
+ protected virtual void OnCollectionChanged (NotifyCollectionChangedEventArgs e) { CollectionChanged?.Invoke (this, e); }
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ _source?.Dispose ();
+
+ base.Dispose (disposing);
+ }
}
///
/// Provides a default implementation of that renders items
/// using .
///
-public class ListWrapper : IListDataSource
+public class ListWrapper : IListDataSource, IDisposable
{
- private readonly int _count;
- private readonly BitArray _marks;
- private readonly IList _source;
+ private int _count;
+ private BitArray _marks;
+ private readonly ObservableCollection _source;
///
- public ListWrapper (IList source)
+ public ListWrapper (ObservableCollection source)
{
if (source is { })
{
_count = source.Count;
_marks = new BitArray (_count);
_source = source;
+ _source.CollectionChanged += Source_CollectionChanged;
Length = GetMaxLengthItem ();
}
}
- ///
- public int Count => _source is { } ? _source.Count : 0;
+ private void Source_CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
+ {
+ CheckAndResizeMarksIfRequired ();
+ CollectionChanged?.Invoke (sender, e);
+ }
+
+ ///
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
///
- public int Length { get; }
+ public int Count => _source?.Count ?? 0;
+
+ ///
+ public int Length { get; private set; }
+
+ void CheckAndResizeMarksIfRequired ()
+ {
+ if (_source != null && _count != _source.Count)
+ {
+ _count = _source.Count;
+ BitArray newMarks = new BitArray (_count);
+ for (var i = 0; i < Math.Min (_marks.Length, newMarks.Length); i++)
+ {
+ newMarks [i] = _marks [i];
+ }
+ _marks = newMarks;
+
+ Length = GetMaxLengthItem ();
+ }
+ }
///
public void Render (
@@ -886,25 +962,25 @@ public class ListWrapper : IListDataSource
)
{
container.Move (Math.Max (col - start, 0), line);
- object t = _source? [item];
- if (t is null)
+ if (_source is { })
{
- RenderUstr (driver, "", col, line, width);
- }
- else
- {
- if (t is string u)
+ object t = _source [item];
+
+ if (t is null)
{
- RenderUstr (driver, u, col, line, width, start);
- }
- else if (t is string s)
- {
- RenderUstr (driver, s, col, line, width, start);
+ RenderUstr (driver, "", col, line, width);
}
else
{
- RenderUstr (driver, t.ToString (), col, line, width, start);
+ if (t is string s)
+ {
+ RenderUstr (driver, s, col, line, width, start);
+ }
+ else
+ {
+ RenderUstr (driver, t.ToString (), col, line, width, start);
+ }
}
}
}
@@ -1002,7 +1078,7 @@ public class ListWrapper : IListDataSource
private void RenderUstr (ConsoleDriver driver, string ustr, int col, int line, int width, int start = 0)
{
string str = start > ustr.GetColumns () ? string.Empty : ustr.Substring (Math.Min (start, ustr.ToRunes ().Length - 1));
- string u = TextFormatter.ClipAndJustify (str, width, TextAlignment.Left);
+ string u = TextFormatter.ClipAndJustify (str, width, Alignment.Start);
driver.AddStr (u);
width -= u.GetColumns ();
@@ -1011,4 +1087,13 @@ public class ListWrapper : IListDataSource
driver.AddRune ((Rune)' ');
}
}
+
+ ///
+ public void Dispose ()
+ {
+ if (_source is { })
+ {
+ _source.CollectionChanged -= Source_CollectionChanged;
+ }
+ }
}
diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs
index 11bc71c7e..89d8548e9 100644
--- a/Terminal.Gui/Views/Menu/Menu.cs
+++ b/Terminal.Gui/Views/Menu/Menu.cs
@@ -1,292 +1,5 @@
namespace Terminal.Gui;
-/// Specifies how a shows selection state.
-[Flags]
-public enum MenuItemCheckStyle
-{
- /// The menu item will be shown normally, with no check indicator. The default.
- NoCheck = 0b_0000_0000,
-
- /// The menu item will indicate checked/un-checked state (see ).
- Checked = 0b_0000_0001,
-
- /// The menu item is part of a menu radio group (see ) and will indicate selected state.
- Radio = 0b_0000_0010
-}
-
-///
-/// A has title, an associated help text, and an action to execute on activation. MenuItems
-/// can also have a checked indicator (see ).
-///
-public class MenuItem
-{
- private readonly ShortcutHelper _shortcutHelper;
- private bool _allowNullChecked;
- private MenuItemCheckStyle _checkType;
-
- private string _title;
-
- // TODO: Update to use Key instead of KeyCode
- /// Initializes a new instance of
- public MenuItem (KeyCode shortcut = KeyCode.Null) : this ("", "", null, null, null, shortcut) { }
-
- // TODO: Update to use Key instead of KeyCode
- /// Initializes a new instance of .
- /// Title for the menu item.
- /// Help text to display.
- /// Action to invoke when the menu item is activated.
- /// Function to determine if the action can currently be executed.
- /// The of this menu item.
- /// The keystroke combination.
- public MenuItem (
- string title,
- string help,
- Action action,
- Func canExecute = null,
- MenuItem parent = null,
- KeyCode shortcut = KeyCode.Null
- )
- {
- Title = title ?? "";
- Help = help ?? "";
- Action = action;
- CanExecute = canExecute;
- Parent = parent;
- _shortcutHelper = new ShortcutHelper ();
-
- if (shortcut != KeyCode.Null)
- {
- Shortcut = shortcut;
- }
- }
-
- /// Gets or sets the action to be invoked when the menu item is triggered.
- /// Method to invoke.
- public Action Action { get; set; }
-
- ///
- /// Used only if is of type. If
- /// allows to be null, true or false. If only
- /// allows to be true or false.
- ///
- public bool AllowNullChecked
- {
- get => _allowNullChecked;
- set
- {
- _allowNullChecked = value;
- Checked ??= false;
- }
- }
-
- ///
- /// Gets or sets the action to be invoked to determine if the menu can be triggered. If
- /// returns the menu item will be enabled. Otherwise, it will be disabled.
- ///
- /// Function to determine if the action is can be executed or not.
- public Func CanExecute { get; set; }
-
- ///
- /// Sets or gets whether the shows a check indicator or not. See
- /// .
- ///
- public bool? Checked { set; get; }
-
- ///
- /// Sets or gets the of a menu item where is set to
- /// .
- ///
- public MenuItemCheckStyle CheckType
- {
- get => _checkType;
- set
- {
- _checkType = value;
-
- if (_checkType == MenuItemCheckStyle.Checked && !_allowNullChecked && Checked is null)
- {
- Checked = false;
- }
- }
- }
-
- /// Gets or sets arbitrary data for the menu item.
- /// This property is not used internally.
- public object Data { get; set; }
-
- /// Gets or sets the help text for the menu item. The help text is drawn to the right of the .
- /// The help text.
- public string Help { get; set; }
-
- /// Gets the parent for this .
- /// The parent.
- public MenuItem Parent { get; set; }
-
- /// Gets or sets the title of the menu item .
- /// The title.
- public string Title
- {
- get => _title;
- set
- {
- if (_title == value)
- {
- return;
- }
-
- _title = value;
- GetHotKey ();
- }
- }
-
- /// Gets if this is from a sub-menu.
- internal bool IsFromSubMenu => Parent != null;
-
- internal int TitleLength => GetMenuBarItemLength (Title);
-
- //
- // ┌─────────────────────────────┐
- // │ Quit Quit UI Catalog Ctrl+Q │
- // └─────────────────────────────┘
- // ┌─────────────────┐
- // │ ◌ TopLevel Alt+T │
- // └─────────────────┘
- // TODO: Replace the `2` literals with named constants
- internal int Width => 1
- + // space before Title
- TitleLength
- + 2
- + // space after Title - BUGBUG: This should be 1
- (Checked == true || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio)
- ? 2
- : 0)
- + // check glyph + space
- (Help.GetColumns () > 0 ? 2 + Help.GetColumns () : 0)
- + // Two spaces before Help
- (ShortcutTag.GetColumns () > 0
- ? 2 + ShortcutTag.GetColumns ()
- : 0); // Pad two spaces before shortcut tag (which are also aligned right)
-
- /// Merely a debugging aid to see the interaction with main.
- public bool GetMenuBarItem () { return IsFromSubMenu; }
-
- /// Merely a debugging aid to see the interaction with main.
- public MenuItem GetMenuItem () { return this; }
-
- ///
- /// Returns if the menu item is enabled. This method is a wrapper around
- /// .
- ///
- public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
-
- ///
- /// Toggle the between three states if is
- /// or between two states if is .
- ///
- public void ToggleChecked ()
- {
- if (_checkType != MenuItemCheckStyle.Checked)
- {
- throw new InvalidOperationException ("This isn't a Checked MenuItemCheckStyle!");
- }
-
- bool? previousChecked = Checked;
-
- if (AllowNullChecked)
- {
- Checked = previousChecked switch
- {
- null => true,
- true => false,
- false => null
- };
- }
- else
- {
- Checked = !Checked;
- }
- }
-
- private static int GetMenuBarItemLength (string title)
- {
- return title.EnumerateRunes ()
- .Where (ch => ch != MenuBar.HotKeySpecifier)
- .Sum (ch => Math.Max (ch.GetColumns (), 1));
- }
-
- #region Keyboard Handling
-
- // TODO: Update to use Key instead of Rune
- ///
- /// The HotKey is used to activate a with the keyboard. HotKeys are defined by prefixing the
- /// of a MenuItem with an underscore ('_').
- ///
- /// Pressing Alt-Hotkey for a (menu items on the menu bar) works even if the menu is
- /// not active). Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
- ///
- ///
- /// For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the
- /// File menu. Pressing the N key will then activate the New MenuItem.
- ///
- /// See also which enable global key-bindings to menu items.
- ///
- public Rune HotKey { get; set; }
-
- private void GetHotKey ()
- {
- var nextIsHot = false;
-
- foreach (char x in _title)
- {
- if (x == MenuBar.HotKeySpecifier.Value)
- {
- nextIsHot = true;
- }
- else
- {
- if (nextIsHot)
- {
- HotKey = (Rune)char.ToUpper (x);
-
- break;
- }
-
- nextIsHot = false;
- HotKey = default (Rune);
- }
- }
- }
-
- // TODO: Update to use Key instead of KeyCode
- ///
- /// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the
- /// that is the parent of the or this
- /// .
- ///
- /// The will be drawn on the MenuItem to the right of the and
- /// text. See .
- ///
- ///
- public KeyCode Shortcut
- {
- get => _shortcutHelper.Shortcut;
- set
- {
- if (_shortcutHelper.Shortcut != value && (ShortcutHelper.PostShortcutValidation (value) || value == KeyCode.Null))
- {
- _shortcutHelper.Shortcut = value;
- }
- }
- }
-
- /// Gets the text describing the keystroke combination defined by .
- public string ShortcutTag => _shortcutHelper.Shortcut == KeyCode.Null
- ? string.Empty
- : Key.ToString (_shortcutHelper.Shortcut, MenuBar.ShortcutDelimiter);
-
- #endregion Keyboard Handling
-}
-
///
/// An internal class used to represent a menu pop-up menu. Created and managed by and
/// .
@@ -408,15 +121,6 @@ internal sealed class Menu : View
);
AddKeyBindings (_barItems);
-#if SUPPORT_ALT_TO_ACTIVATE_MENU
- Initialized += (s, e) =>
- {
- if (SuperView is { })
- {
- SuperView.KeyUp += SuperView_KeyUp;
- }
- };
-#endif
}
public Menu ()
@@ -462,9 +166,9 @@ internal sealed class Menu : View
return true;
}
);
- AddCommand (Command.Select, () => _host?.SelectItem (_menuItemToSelect));
- AddCommand (Command.ToggleExpandCollapse, () => SelectOrRun ());
- AddCommand (Command.HotKey, () => _host?.SelectItem (_menuItemToSelect));
+ AddCommand (Command.Select, ctx => _host?.SelectItem (ctx.KeyBinding?.Context as MenuItem));
+ AddCommand (Command.ToggleExpandCollapse, ctx => ExpandCollapse (ctx.KeyBinding?.Context as MenuItem));
+ AddCommand (Command.HotKey, ctx => _host?.SelectItem (ctx.KeyBinding?.Context as MenuItem));
// Default key bindings for this view
KeyBindings.Add (Key.CursorUp, Command.LineUp);
@@ -473,27 +177,8 @@ internal sealed class Menu : View
KeyBindings.Add (Key.CursorRight, Command.Right);
KeyBindings.Add (Key.Esc, Command.Cancel);
KeyBindings.Add (Key.Enter, Command.Accept);
- KeyBindings.Add (Key.F9, KeyBindingScope.HotKey, Command.ToggleExpandCollapse);
-
- KeyBindings.Add (
- KeyCode.CtrlMask | KeyCode.Space,
- KeyBindingScope.HotKey,
- Command.ToggleExpandCollapse
- );
}
-#if SUPPORT_ALT_TO_ACTIVATE_MENU
- void SuperView_KeyUp (object sender, KeyEventArgs e)
- {
- if (SuperView is null || SuperView.CanFocus == false || SuperView.Visible == false)
- {
- return;
- }
-
- _host.AltKeyUpHandler (e);
- }
-#endif
-
private void AddKeyBindings (MenuBarItem menuBarItem)
{
if (menuBarItem is null || menuBarItem.Children is null)
@@ -503,16 +188,18 @@ internal sealed class Menu : View
foreach (MenuItem menuItem in menuBarItem.Children.Where (m => m is { }))
{
- KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, Command.ToggleExpandCollapse);
+ KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuItem);
- KeyBindings.Add (
- (KeyCode)menuItem.HotKey.Value | KeyCode.AltMask,
- Command.ToggleExpandCollapse
- );
+ if ((KeyCode)menuItem.HotKey.Value != KeyCode.Null)
+ {
+ KeyBindings.Add ((KeyCode)menuItem.HotKey.Value, keyBinding);
+ KeyBindings.Add ((KeyCode)menuItem.HotKey.Value | KeyCode.AltMask, keyBinding);
+ }
if (menuItem.Shortcut != KeyCode.Null)
{
- KeyBindings.Add (menuItem.Shortcut, KeyBindingScope.HotKey, Command.Select);
+ keyBinding = new ([Command.Select], KeyBindingScope.HotKey, menuItem);
+ KeyBindings.Add (menuItem.Shortcut, keyBinding);
}
MenuBarItem subMenu = menuBarItem.SubMenu (menuItem);
@@ -520,25 +207,29 @@ internal sealed class Menu : View
}
}
- private int _menuBarItemToActivate = -1;
- private MenuItem _menuItemToSelect;
-
- /// Called when a key bound to Command.Select is pressed. This means a hot key was pressed.
+ /// Called when a key bound to Command.ToggleExpandCollapse is pressed. This means a hot key was pressed.
///
- private bool SelectOrRun ()
+ private bool ExpandCollapse (MenuItem menuItem)
{
if (!IsInitialized || !Visible)
{
return true;
}
- if (_menuBarItemToActivate != -1)
+
+ for (var c = 0; c < _barItems.Children.Length; c++)
{
- _host.Activate (1, _menuBarItemToActivate);
+ if (_barItems.Children [c] == menuItem)
+ {
+ _currentChild = c;
+
+ break;
+ }
}
- else if (_menuItemToSelect is { })
+
+ if (menuItem is { })
{
- var m = _menuItemToSelect as MenuBarItem;
+ var m = menuItem as MenuBarItem;
if (m?.Children?.Length > 0)
{
@@ -566,7 +257,7 @@ internal sealed class Menu : View
}
else
{
- _host.SelectItem (_menuItemToSelect);
+ _host.SelectItem (menuItem);
}
}
else if (_host.IsMenuOpen)
@@ -578,82 +269,12 @@ internal sealed class Menu : View
_host.OpenMenu ();
}
- //_openedByHotKey = true;
return true;
}
///
public override bool? OnInvokingKeyBindings (Key keyEvent)
{
- // This is a bit of a hack. We want to handle the key bindings for menu bar but
- // InvokeKeyBindings doesn't pass any context so we can't tell which item it is for.
- // So before we call the base class we set SelectedItem appropriately.
-
- KeyCode key = keyEvent.KeyCode;
-
- if (KeyBindings.TryGet (key, out _))
- {
- _menuBarItemToActivate = -1;
- _menuItemToSelect = null;
-
- MenuItem [] children = _barItems.Children;
-
- if (children is null)
- {
- return base.OnInvokingKeyBindings (keyEvent);
- }
-
- // Search for shortcuts first. If there's a shortcut, we don't want to activate the menu item.
- foreach (MenuItem c in children)
- {
- if (key == c?.Shortcut)
- {
- _menuBarItemToActivate = -1;
- _menuItemToSelect = c;
- //keyEvent.Scope = KeyBindingScope.HotKey;
-
- return base.OnInvokingKeyBindings (keyEvent);
- }
-
- MenuBarItem subMenu = _barItems.SubMenu (c);
-
- if (FindShortcutInChildMenu (key, subMenu))
- {
- //keyEvent.Scope = KeyBindingScope.HotKey;
-
- return base.OnInvokingKeyBindings (keyEvent);
- }
- }
-
- // Search for hot keys next.
- for (var c = 0; c < children.Length; c++)
- {
- int hotKeyValue = children [c]?.HotKey.Value ?? default (int);
- var hotKey = (KeyCode)hotKeyValue;
-
- if (hotKey == KeyCode.Null)
- {
- continue;
- }
-
- bool matches = key == hotKey || key == (hotKey | KeyCode.AltMask);
-
- if (!_host.IsMenuOpen)
- {
- // If the menu is open, only match if Alt is not pressed.
- matches = key == hotKey;
- }
-
- if (matches)
- {
- _menuItemToSelect = children [c];
- _currentChild = c;
-
- return base.OnInvokingKeyBindings (keyEvent);
- }
- }
- }
-
bool? handled = base.OnInvokingKeyBindings (keyEvent);
if (handled is { } && (bool)handled)
@@ -661,34 +282,11 @@ internal sealed class Menu : View
return true;
}
+ // TODO: Determine if there's a cleaner way to handle this
// This supports the case where the menu bar is a context menu
return _host.OnInvokingKeyBindings (keyEvent);
}
- private bool FindShortcutInChildMenu (KeyCode key, MenuBarItem menuBarItem)
- {
- if (menuBarItem?.Children is null)
- {
- return false;
- }
-
- foreach (MenuItem menuItem in menuBarItem.Children)
- {
- if (key == menuItem?.Shortcut)
- {
- _menuBarItemToActivate = -1;
- _menuItemToSelect = menuItem;
-
- return true;
- }
-
- MenuBarItem subMenu = menuBarItem.SubMenu (menuItem);
- FindShortcutInChildMenu (key, subMenu);
- }
-
- return false;
- }
-
private void Current_TerminalResized (object sender, SizeChangedEventArgs e)
{
if (_host.IsMenuOpen)
@@ -727,6 +325,7 @@ internal sealed class Menu : View
View view = a.View ?? this;
Point boundsPoint = view.ScreenToViewport (new (a.Position.X, a.Position.Y));
+
var me = new MouseEvent
{
Position = boundsPoint,
@@ -786,12 +385,12 @@ internal sealed class Menu : View
Driver.SetAttribute (
item is null ? GetNormalColor () :
- i == _currentChild ? GetFocusColor() : GetNormalColor ()
+ i == _currentChild ? GetFocusColor () : GetNormalColor ()
);
if (item is null && BorderStyle != LineStyle.None)
{
- var s = ViewportToScreen (new Point (-1, i));
+ Point s = ViewportToScreen (new Point (-1, i));
Driver.Move (s.X, s.Y);
Driver.AddRune (Glyphs.LeftTee);
}
@@ -839,7 +438,7 @@ internal sealed class Menu : View
{
if (BorderStyle != LineStyle.None && SuperView?.Frame.Right - Frame.X > Frame.Width)
{
- var s = ViewportToScreen (new Point (Frame.Width - 2, i));
+ Point s = ViewportToScreen (new Point (Frame.Width - 2, i));
Driver.Move (s.X, s.Y);
Driver.AddRune (Glyphs.RightTee);
}
@@ -876,7 +475,8 @@ internal sealed class Menu : View
textToDraw = item.Title;
}
- var screen = ViewportToScreen (new Point(0 , i));
+ Point screen = ViewportToScreen (new Point (0, i));
+
if (screen.X < Driver.Cols)
{
Driver.Move (screen.X + 1, screen.Y);
@@ -890,12 +490,12 @@ internal sealed class Menu : View
var tf = new TextFormatter
{
AutoSize = true,
- Alignment = TextAlignment.Centered, HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw
+ Alignment = Alignment.Center, HotKeySpecifier = MenuBar.HotKeySpecifier, Text = textToDraw
};
// The -3 is left/right border + one space (not sure what for)
tf.Draw (
- ViewportToScreen (new Rectangle(1, i, Frame.Width - 3, 1)),
+ ViewportToScreen (new Rectangle (1, i, Frame.Width - 3, 1)),
i == _currentChild ? GetFocusColor () : GetNormalColor (),
i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty
@@ -934,7 +534,7 @@ internal sealed class Menu : View
Driver.Clip = savedClip;
- // PositionCursor ();
+ // PositionCursor ();
}
private void Current_DrawContentComplete (object sender, DrawEventArgs e)
@@ -953,13 +553,10 @@ internal sealed class Menu : View
{
return _host?.PositionCursor ();
}
- else
- {
- Move (2, 1 + _currentChild);
- return null; // Don't show the cursor
+ Move (2, 1 + _currentChild);
- }
+ return null; // Don't show the cursor
}
return _host?.PositionCursor ();
@@ -1031,11 +628,11 @@ internal sealed class Menu : View
_currentChild = 0;
}
- if (this != _host.openCurrentMenu && _barItems.Children [_currentChild]?.IsFromSubMenu == true && _host._selectedSub > -1)
+ if (this != _host.OpenCurrentMenu && _barItems.Children [_currentChild]?.IsFromSubMenu == true && _host._selectedSub > -1)
{
_host.PreviousMenu (true);
_host.SelectEnabledItem (_barItems.Children, _currentChild, out _currentChild);
- _host.openCurrentMenu = this;
+ _host.OpenCurrentMenu = this;
}
MenuItem item = _barItems.Children [_currentChild];
@@ -1096,7 +693,7 @@ internal sealed class Menu : View
if (_host.UseKeysUpDownAsKeysLeftRight && !_host.UseSubMenusSingleFrame)
{
- if ((_currentChild == -1 || this != _host.openCurrentMenu)
+ if ((_currentChild == -1 || this != _host.OpenCurrentMenu)
&& _barItems.Children [_currentChild + 1].IsFromSubMenu
&& _host._selectedSub > -1)
{
@@ -1106,7 +703,7 @@ internal sealed class Menu : View
if (_currentChild > 0)
{
_currentChild--;
- _host.openCurrentMenu = this;
+ _host.OpenCurrentMenu = this;
}
break;
@@ -1176,7 +773,7 @@ internal sealed class Menu : View
_host?.SetNeedsDisplay ();
}
- protected internal override bool OnMouseEvent (MouseEvent me)
+ protected internal override bool OnMouseEvent (MouseEvent me)
{
if (!_host._handled && !_host.HandleGrabView (me, this))
{
@@ -1285,8 +882,8 @@ internal sealed class Menu : View
}
if (pos == -1
- && this != _host.openCurrentMenu
- && subMenu.Children != _host.openCurrentMenu._barItems.Children
+ && this != _host.OpenCurrentMenu
+ && subMenu.Children != _host.OpenCurrentMenu._barItems.Children
&& !_host.CloseMenu (false, true))
{
return false;
@@ -1307,33 +904,6 @@ internal sealed class Menu : View
return true;
}
- private int GetSubMenuIndex (MenuBarItem subMenu)
- {
- int pos = -1;
-
- if (Subviews.Count == 0)
- {
- return pos;
- }
-
- Menu v = null;
-
- foreach (View menu in Subviews)
- {
- if (((Menu)menu)._barItems == subMenu)
- {
- v = (Menu)menu;
- }
- }
-
- if (v is { })
- {
- pos = Subviews.IndexOf (v);
- }
-
- return pos;
- }
-
protected override void Dispose (bool disposing)
{
if (Application.Current is { })
diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs
index e25798bac..08b045f81 100644
--- a/Terminal.Gui/Views/Menu/MenuBar.cs
+++ b/Terminal.Gui/Views/Menu/MenuBar.cs
@@ -1,191 +1,5 @@
namespace Terminal.Gui;
-///
-/// is a menu item on . MenuBarItems do not support
-/// .
-///
-public class MenuBarItem : MenuItem
-{
- /// Initializes a new as a .
- /// Title for the menu item.
- /// Help text to display. Will be displayed next to the Title surrounded by parentheses.
- /// Action to invoke when the menu item is activated.
- /// Function to determine if the action can currently be executed.
- /// The parent of this if exist, otherwise is null.
- public MenuBarItem (
- string title,
- string help,
- Action action,
- Func canExecute = null,
- MenuItem parent = null
- ) : base (title, help, action, canExecute, parent)
- {
- SetInitialProperties (title, null, null, true);
- }
-
- /// Initializes a new .
- /// Title for the menu item.
- /// The items in the current menu.
- /// The parent of this if exist, otherwise is null.
- public MenuBarItem (string title, MenuItem [] children, MenuItem parent = null) { SetInitialProperties (title, children, parent); }
-
- /// Initializes a new with separate list of items.
- /// Title for the menu item.
- /// The list of items in the current menu.
- /// The parent of this if exist, otherwise is null.
- public MenuBarItem (string title, List
- /// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
+ /// The index of the selected button, or -1 if the user pressed to close the MessageBox.
/// Width for the window.
/// Height for the window.
/// Title for the query.
- /// Message to display, might contain multiple lines.
+ /// Message to display; might contain multiple lines. The message will be word=wrapped by default.
/// Index of the default button.
- /// If wrap the message or not.
+ /// If wrap the message or not.
/// Array of buttons to add.
///
/// Use instead; it automatically sizes the MessageBox based on
@@ -137,22 +151,22 @@ public static class MessageBox
string title,
string message,
int defaultButton = 0,
- bool wrapMessagge = true,
+ bool wrapMessage = true,
params string [] buttons
)
{
- return QueryFull (true, width, height, title, message, defaultButton, wrapMessagge, buttons);
+ return QueryFull (true, width, height, title, message, defaultButton, wrapMessage, buttons);
}
///
/// Presents an error with the specified title and message and a list of buttons to show
/// to the user.
///
- /// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
+ /// The index of the selected button, or -1 if the user pressed to close the MessageBox.
/// Title for the query.
- /// Message to display, might contain multiple lines.
+ /// Message to display; might contain multiple lines. The message will be word=wrapped by default.
/// Index of the default button.
- /// If wrap the message or not.
+ /// If wrap the message or not. The default is
/// Array of buttons to add.
///
/// The message box will be vertically and horizontally centered in the container and the size will be
@@ -162,26 +176,25 @@ public static class MessageBox
string title,
string message,
int defaultButton = 0,
- bool wrapMessagge = true,
+ bool wrapMessage = true,
params string [] buttons
)
{
- return QueryFull (true, 0, 0, title, message, defaultButton, wrapMessagge, buttons);
+ return QueryFull (true, 0, 0, title, message, defaultButton, wrapMessage, buttons);
}
///
- /// Presents a normal with the specified title and message and a list of buttons to show
- /// to the user.
+ /// Presents a with the specified title and message and a list of buttons.
///
- /// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
- /// Width for the window.
- /// Height for the window.
- /// Title for the query.
- /// Message to display, might contain multiple lines.
+ /// The index of the selected button, or -1 if the user pressed to close the MessageBox.
+ /// Width for the MessageBox.
+ /// Height for the MessageBox.
+ /// Title for the MessageBox.
+ /// Message to display; might contain multiple lines. The message will be word=wrapped by default.
/// Array of buttons to add.
///
- /// Use instead; it automatically sizes the MessageBox based on the
- /// contents.
+ /// Use instead; it automatically sizes the MessageBox based on
+ /// the contents.
///
public static int Query (int width, int height, string title, string message, params string [] buttons)
{
@@ -189,33 +202,43 @@ public static class MessageBox
}
///
- /// Presents an error with the specified title and message and a list of buttons to show
- /// to the user.
+ /// Presents a with the specified title and message and a list of buttons.
///
- /// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
- /// Title for the query.
- /// Message to display, might contain multiple lines.
+ /// The index of the selected button, or -1 if the user pressed to close the MessageBox.
+ /// Title for the MessageBox.
+ /// Message to display; might contain multiple lines. The message will be word=wrapped by default.
/// Array of buttons to add.
///
+ ///
/// The message box will be vertically and horizontally centered in the container and the size will be
- /// automatically determined from the size of the message and buttons.
+ /// automatically determined from the size of the title, message. and buttons.
+ ///
+ ///
+ /// Use instead; it automatically sizes the MessageBox based on
+ /// the contents.
+ ///
///
public static int Query (string title, string message, params string [] buttons) { return QueryFull (false, 0, 0, title, message, 0, true, buttons); }
///
- /// Presents a normal with the specified title and message and a list of buttons to show
- /// to the user.
+ /// Presents a with the specified title and message and a list of buttons.
///
- /// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
+ /// The index of the selected button, or -1 if the user pressed to close the MessageBox.
/// Width for the window.
/// Height for the window.
- /// Title for the query.
- /// Message to display, might contain multiple lines.
+ /// Title for the MessageBox.
+ /// Message to display; might contain multiple lines. The message will be word=wrapped by default.
/// Index of the default button.
/// Array of buttons to add.
///
- /// Use instead; it automatically sizes the MessageBox based on the
- /// contents.
+ ///
+ /// The message box will be vertically and horizontally centered in the container and the size will be
+ /// automatically determined from the size of the title, message. and buttons.
+ ///
+ ///
+ /// Use instead; it automatically sizes the MessageBox based on
+ /// the contents.
+ ///
///
public static int Query (
int width,
@@ -230,12 +253,11 @@ public static class MessageBox
}
///
- /// Presents an error with the specified title and message and a list of buttons to show
- /// to the user.
+ /// Presents a with the specified title and message and a list of buttons.
///
- /// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
- /// Title for the query.
- /// Message to display, might contain multiple lines.
+ /// The index of the selected button, or -1 if the user pressed to close the MessageBox.
+ /// Title for the MessageBox.
+ /// Message to display; might contain multiple lines. The message will be word=wrapped by default.
/// Index of the default button.
/// Array of buttons to add.
///
@@ -248,16 +270,16 @@ public static class MessageBox
}
///
- /// Presents a normal with the specified title and message and a list of buttons to show
+ /// Presents a with the specified title and message and a list of buttons to show
/// to the user.
///
- /// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
+ /// The index of the selected button, or -1 if the user pressed to close the MessageBox.
/// Width for the window.
/// Height for the window.
/// Title for the query.
/// Message to display, might contain multiple lines.
/// Index of the default button.
- /// If wrap the message or not.
+ /// If wrap the message or not.
/// Array of buttons to add.
///
/// Use instead; it automatically sizes the MessageBox based on the
@@ -269,27 +291,23 @@ public static class MessageBox
string title,
string message,
int defaultButton = 0,
- bool wrapMessagge = true,
+ bool wrapMessage = true,
params string [] buttons
)
{
- return QueryFull (false, width, height, title, message, defaultButton, wrapMessagge, buttons);
+ return QueryFull (false, width, height, title, message, defaultButton, wrapMessage, buttons);
}
///
- /// Presents an error with the specified title and message and a list of buttons to show
+ /// Presents a with the specified title and message and a list of buttons to show
/// to the user.
///
- /// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
+ /// The index of the selected button, or -1 if the user pressed to close the MessageBox.
/// Title for the query.
/// Message to display, might contain multiple lines.
/// Index of the default button.
/// If wrap the message or not.
/// Array of buttons to add.
- ///
- /// The message box will be vertically and horizontally centered in the container and the size will be
- /// automatically determined from the size of the message and buttons.
- ///
public static int Query (
string title,
string message,
@@ -325,7 +343,10 @@ public static class MessageBox
foreach (string s in buttons)
{
- var b = new Button { Text = s };
+ var b = new Button
+ {
+ Text = s,
+ };
if (count == defaultButton)
{
@@ -337,15 +358,15 @@ public static class MessageBox
}
}
- Dialog d;
-
- d = new Dialog
+ var d = new Dialog
{
- Buttons = buttonList.ToArray (),
Title = title,
+ Buttons = buttonList.ToArray (),
+ ButtonAlignment = Alignment.Center,
+ ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems,
BorderStyle = DefaultBorderStyle,
- Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: Dim.Percent(60)),
- Height = Dim.Auto (DimAutoStyle.Content),
+ Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 1, Dim.Percent (90)),
+ Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 1, Dim.Percent (90)),
};
if (width != 0)
@@ -358,22 +379,18 @@ public static class MessageBox
d.Height = height;
}
- if (useErrorColors)
- {
- d.ColorScheme = Colors.ColorSchemes ["Error"];
- }
- else
- {
- d.ColorScheme = Colors.ColorSchemes ["Dialog"];
- }
+ d.ColorScheme = useErrorColors ? Colors.ColorSchemes ["Error"] : Colors.ColorSchemes ["Dialog"];
var messageLabel = new Label
{
+ HotKeySpecifier = new Rune ('\xFFFF'),
+ Width = Dim.Auto (DimAutoStyle.Text),
+ Height = Dim.Auto (DimAutoStyle.Text),
Text = message,
- TextAlignment = TextAlignment.Centered,
+ TextAlignment = Alignment.Center,
X = Pos.Center (),
Y = 0,
- // ColorScheme = Colors.ColorSchemes ["Error"]
+ //ColorScheme = Colors.ColorSchemes ["Error"],
};
messageLabel.TextFormatter.WordWrap = wrapMessage;
@@ -381,15 +398,16 @@ public static class MessageBox
if (wrapMessage)
{
+ int buttonHeight = buttonList.Count > 0 ? buttonList [0].Frame.Height : 0;
+
messageLabel.Width = Dim.Fill ();
- messageLabel.Height = Dim.Fill (1);
- int GetWrapSize ()
+ messageLabel.Height = Dim.Func (() => GetWrapSize ().Height);
+ Size GetWrapSize ()
{
// A bit of a hack to get the height of the wrapped text.
- messageLabel.TextFormatter.Size = new (d.ContentSize.Width, 1000);
- return messageLabel.TextFormatter.FormatAndGetSize ().Height;
+ messageLabel.TextFormatter.Size = d.GetContentSize () with { Height = 1000 };
+ return messageLabel.TextFormatter.FormatAndGetSize ();
}
- d.Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: Dim.Func (GetWrapSize) + 1);
}
d.Add (messageLabel);
diff --git a/Terminal.Gui/Views/OrientationEventArgs.cs b/Terminal.Gui/Views/OrientationEventArgs.cs
new file mode 100644
index 000000000..8a633ca83
--- /dev/null
+++ b/Terminal.Gui/Views/OrientationEventArgs.cs
@@ -0,0 +1,19 @@
+namespace Terminal.Gui;
+
+/// for events.
+public class OrientationEventArgs : EventArgs
+{
+ /// Constructs a new instance.
+ /// the new orientation
+ public OrientationEventArgs (Orientation orientation)
+ {
+ Orientation = orientation;
+ Cancel = false;
+ }
+
+ /// If set to true, the orientation change operation will be canceled, if applicable.
+ public bool Cancel { get; set; }
+
+ /// The new orientation.
+ public Orientation Orientation { get; set; }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs
index f599b299f..80b7545b7 100644
--- a/Terminal.Gui/Views/ProgressBar.cs
+++ b/Terminal.Gui/Views/ProgressBar.cs
@@ -175,7 +175,7 @@ public class ProgressBar : View
if (ProgressBarFormat != ProgressBarFormat.Simple && !_isActivity)
{
- var tf = new TextFormatter { Alignment = TextAlignment.Centered, Text = Text, AutoSize = true };
+ var tf = new TextFormatter { Alignment = Alignment.Center, Text = Text, AutoSize = true };
var attr = new Attribute (ColorScheme.HotNormal.Foreground, ColorScheme.HotNormal.Background);
if (_fraction > .5)
diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs
index a42f6d840..efdfa5260 100644
--- a/Terminal.Gui/Views/RadioGroup.cs
+++ b/Terminal.Gui/Views/RadioGroup.cs
@@ -11,8 +11,7 @@ public class RadioGroup : View
private int _selected;
///
- /// Initializes a new instance of the class using
- /// layout.
+ /// Initializes a new instance of the class.
///
public RadioGroup ()
{
@@ -66,16 +65,32 @@ public class RadioGroup : View
Command.Accept,
() =>
{
- SelectItem ();
+ SelectedItem = _cursor;
+
return !OnAccept ();
}
);
+ AddCommand (
+ Command.HotKey,
+ ctx =>
+ {
+ SetFocus ();
+ if (ctx.KeyBinding?.Context is { } && (int)ctx.KeyBinding?.Context! < _radioLabels.Count)
+ {
+ SelectedItem = (int)ctx.KeyBinding?.Context!;
+
+ return !OnAccept();
+ }
+
+ return true;
+ });
+
SetupKeyBindings ();
LayoutStarted += RadioGroup_LayoutStarted;
- HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.HighlightStyle.Pressed;
+ HighlightStyle = HighlightStyle.PressedOutside | HighlightStyle.Pressed;
MouseClick += RadioGroup_MouseClick;
}
@@ -85,6 +100,7 @@ public class RadioGroup : View
private void SetupKeyBindings ()
{
KeyBindings.Clear ();
+
// Default keybindings for this view
if (Orientation == Orientation.Vertical)
{
@@ -96,6 +112,7 @@ public class RadioGroup : View
KeyBindings.Add (Key.CursorLeft, Command.LineUp);
KeyBindings.Add (Key.CursorRight, Command.LineDown);
}
+
KeyBindings.Add (Key.Home, Command.TopHome);
KeyBindings.Add (Key.End, Command.BottomEnd);
KeyBindings.Add (Key.Space, Command.Accept);
@@ -180,11 +197,13 @@ public class RadioGroup : View
int prevCount = _radioLabels.Count;
_radioLabels = value.ToList ();
- foreach (string label in _radioLabels)
+ for (var index = 0; index < _radioLabels.Count; index++)
{
+ string label = _radioLabels [index];
+
if (TextFormatter.FindHotKey (label, HotKeySpecifier, out _, out Key hotKey))
{
- AddKeyBindingsForHotKey (Key.Empty, hotKey);
+ AddKeyBindingsForHotKey (Key.Empty, hotKey, index);
}
}
@@ -193,7 +212,7 @@ public class RadioGroup : View
}
}
- ///
+ ///
public override string Text
{
get
@@ -202,6 +221,7 @@ public class RadioGroup : View
{
return string.Empty;
}
+
// Return labels as a CSV string
return string.Join (",", _radioLabels);
}
@@ -221,73 +241,51 @@ public class RadioGroup : View
/// The currently selected item from the list of radio labels
/// The selected.
public int SelectedItem
-{
- get => _selected;
- set
{
- OnSelectedItemChanged (value, SelectedItem);
- _cursor = Math.Max (_selected, 0);
- SetNeedsDisplay ();
- }
-}
-
-///
-public override void OnDrawContent (Rectangle viewport)
-{
- base.OnDrawContent (viewport);
-
- Driver.SetAttribute (GetNormalColor ());
-
- for (var i = 0; i < _radioLabels.Count; i++)
- {
- switch (Orientation)
+ get => _selected;
+ set
{
- case Orientation.Vertical:
- Move (0, i);
-
- break;
- case Orientation.Horizontal:
- Move (_horizontal [i].pos, 0);
-
- break;
+ OnSelectedItemChanged (value, SelectedItem);
+ _cursor = Math.Max (_selected, 0);
+ SetNeedsDisplay ();
}
+ }
+
+ ///
+ public override void OnDrawContent (Rectangle viewport)
+ {
+ base.OnDrawContent (viewport);
- string rl = _radioLabels [i];
Driver.SetAttribute (GetNormalColor ());
- Driver.AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
- TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out Key hotKey);
- if (hotPos != -1 && hotKey != Key.Empty)
+ for (var i = 0; i < _radioLabels.Count; i++)
{
- Rune [] rlRunes = rl.ToRunes ();
-
- for (var j = 0; j < rlRunes.Length; j++)
+ switch (Orientation)
{
- Rune rune = rlRunes [j];
+ case Orientation.Vertical:
+ Move (0, i);
- if (j == hotPos && i == _cursor)
- {
- Application.Driver.SetAttribute (
- HasFocus
- ? ColorScheme.HotFocus
- : GetHotNormalColor ()
- );
- }
- else if (j == hotPos && i != _cursor)
- {
- Application.Driver.SetAttribute (GetHotNormalColor ());
- }
- else if (HasFocus && i == _cursor)
- {
- Application.Driver.SetAttribute (ColorScheme.Focus);
- }
+ break;
+ case Orientation.Horizontal:
+ Move (_horizontal [i].pos, 0);
- if (rune == HotKeySpecifier && j + 1 < rlRunes.Length)
- {
- j++;
- rune = rlRunes [j];
+ break;
+ }
- if (i == _cursor)
+ string rl = _radioLabels [i];
+ Driver.SetAttribute (GetNormalColor ());
+ Driver.AddStr ($"{(i == _selected ? Glyphs.Selected : Glyphs.UnSelected)} ");
+ TextFormatter.FindHotKey (rl, HotKeySpecifier, out int hotPos, out Key hotKey);
+
+ if (hotPos != -1 && hotKey != Key.Empty)
+ {
+ Rune [] rlRunes = rl.ToRunes ();
+
+ for (var j = 0; j < rlRunes.Length; j++)
+ {
+ Rune rune = rlRunes [j];
+
+ if (j == hotPos && i == _cursor)
{
Application.Driver.SetAttribute (
HasFocus
@@ -295,184 +293,177 @@ public override void OnDrawContent (Rectangle viewport)
: GetHotNormalColor ()
);
}
- else if (i != _cursor)
+ else if (j == hotPos && i != _cursor)
{
Application.Driver.SetAttribute (GetHotNormalColor ());
}
+ else if (HasFocus && i == _cursor)
+ {
+ Application.Driver.SetAttribute (ColorScheme.Focus);
+ }
+
+ if (rune == HotKeySpecifier && j + 1 < rlRunes.Length)
+ {
+ j++;
+ rune = rlRunes [j];
+
+ if (i == _cursor)
+ {
+ Application.Driver.SetAttribute (
+ HasFocus
+ ? ColorScheme.HotFocus
+ : GetHotNormalColor ()
+ );
+ }
+ else if (i != _cursor)
+ {
+ Application.Driver.SetAttribute (GetHotNormalColor ());
+ }
+ }
+
+ Application.Driver.AddRune (rune);
+ Driver.SetAttribute (GetNormalColor ());
+ }
+ }
+ else
+ {
+ DrawHotString (rl, HasFocus && i == _cursor, ColorScheme);
+ }
+ }
+ }
+
+ /// Called when the view orientation has changed. Invokes the event.
+ ///
+ /// True of the event was cancelled.
+ public virtual bool OnOrientationChanged (Orientation newOrientation)
+ {
+ var args = new OrientationEventArgs (newOrientation);
+ OrientationChanged?.Invoke (this, args);
+
+ if (!args.Cancel)
+ {
+ _orientation = newOrientation;
+ SetupKeyBindings ();
+ SetContentSize ();
+ }
+
+ return args.Cancel;
+ }
+
+ // TODO: This should be cancelable
+ /// Called whenever the current selected item changes. Invokes the event.
+ ///
+ ///
+ public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
+ {
+ _selected = selectedItem;
+ SelectedItemChanged?.Invoke (this, new (selectedItem, previousSelectedItem));
+ }
+
+ ///
+ /// Fired when the view orientation has changed. Can be cancelled by setting
+ /// to true.
+ ///
+ public event EventHandler OrientationChanged;
+
+ ///
+ public override Point? PositionCursor ()
+ {
+ var x = 0;
+ var y = 0;
+
+ switch (Orientation)
+ {
+ case Orientation.Vertical:
+ y = _cursor;
+
+ break;
+ case Orientation.Horizontal:
+ x = _horizontal [_cursor].pos;
+
+ break;
+
+ default:
+ return null;
+ }
+
+ Move (x, y);
+
+ return null; // Don't show the cursor
+ }
+
+ /// Allow to invoke the after their creation.
+ public void Refresh () { OnSelectedItemChanged (_selected, -1); }
+
+ // TODO: This should use StateEventArgs and should be cancelable.
+ /// Invoked when the selected radio label has changed.
+ public event EventHandler SelectedItemChanged;
+
+ private void MoveDownRight ()
+ {
+ if (_cursor + 1 < _radioLabels.Count)
+ {
+ _cursor++;
+ SetNeedsDisplay ();
+ }
+ else if (_cursor > 0)
+ {
+ _cursor = 0;
+ SetNeedsDisplay ();
+ }
+ }
+
+ private void MoveEnd () { _cursor = Math.Max (_radioLabels.Count - 1, 0); }
+ private void MoveHome () { _cursor = 0; }
+
+ private void MoveUpLeft ()
+ {
+ if (_cursor > 0)
+ {
+ _cursor--;
+ SetNeedsDisplay ();
+ }
+ else if (_radioLabels.Count - 1 > 0)
+ {
+ _cursor = _radioLabels.Count - 1;
+ SetNeedsDisplay ();
+ }
+ }
+
+ private void RadioGroup_LayoutStarted (object sender, EventArgs e) { SetContentSize (); }
+
+ private void SetContentSize ()
+ {
+ switch (_orientation)
+ {
+ case Orientation.Vertical:
+ var width = 0;
+
+ foreach (string s in _radioLabels)
+ {
+ width = Math.Max (s.GetColumns () + 2, width);
}
- Application.Driver.AddRune (rune);
- Driver.SetAttribute (GetNormalColor ());
- }
- }
- else
- {
- DrawHotString (rl, HasFocus && i == _cursor, ColorScheme);
- }
- }
-}
+ SetContentSize (new (width, _radioLabels.Count));
+
+ break;
+
+ case Orientation.Horizontal:
+ _horizontal = new ();
+ var start = 0;
+ var length = 0;
+
+ for (var i = 0; i < _radioLabels.Count; i++)
+ {
+ start += length;
+
+ length = _radioLabels [i].GetColumns () + 2 + (i < _radioLabels.Count - 1 ? _horizontalSpace : 0);
+ _horizontal.Add ((start, length));
+ }
+
+ SetContentSize (new (_horizontal.Sum (item => item.length), 1));
-///
-public override bool? OnInvokingKeyBindings (Key keyEvent)
-{
- // This is a bit of a hack. We want to handle the key bindings for the radio group but
- // InvokeKeyBindings doesn't pass any context so we can't tell if the key binding is for
- // the radio group or for one of the radio buttons. So before we call the base class
- // we set SelectedItem appropriately.
-
- Key key = keyEvent;
-
- if (KeyBindings.TryGet (key, out _))
- {
- // Search RadioLabels
- for (var i = 0; i < _radioLabels.Count; i++)
- {
- if (TextFormatter.FindHotKey (
- _radioLabels [i],
- HotKeySpecifier,
- out _,
- out Key hotKey,
- true
- )
- && key.NoAlt.NoCtrl.NoShift == hotKey)
- {
- SelectedItem = i;
break;
- }
}
}
-
- return base.OnInvokingKeyBindings (keyEvent);
-}
-
-/// Called when the view orientation has changed. Invokes the event.
-///
-/// True of the event was cancelled.
-public virtual bool OnOrientationChanged (Orientation newOrientation)
-{
- var args = new OrientationEventArgs (newOrientation);
- OrientationChanged?.Invoke (this, args);
-
- if (!args.Cancel)
- {
- _orientation = newOrientation;
- SetupKeyBindings ();
- SetContentSize ();
- }
-
- return args.Cancel;
-}
-
-// TODO: This should be cancelable
-/// Called whenever the current selected item changes. Invokes the event.
-///
-///
-public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
-{
- _selected = selectedItem;
- SelectedItemChanged?.Invoke (this, new SelectedItemChangedArgs (selectedItem, previousSelectedItem));
-}
-
-///
-/// Fired when the view orientation has changed. Can be cancelled by setting
-/// to true.
-///
-public event EventHandler OrientationChanged;
-
-///
-public override Point? PositionCursor ()
-{
- int x = 0;
- int y = 0;
- switch (Orientation)
- {
- case Orientation.Vertical:
- y = _cursor;
-
- break;
- case Orientation.Horizontal:
- x = _horizontal [_cursor].pos;
-
- break;
-
- default:
- return null;
- }
-
- Move (x, y);
- return null; // Don't show the cursor
-}
-
-/// Allow to invoke the after their creation.
-public void Refresh () { OnSelectedItemChanged (_selected, -1); }
-
-// TODO: This should use StateEventArgs and should be cancelable.
-/// Invoked when the selected radio label has changed.
-public event EventHandler SelectedItemChanged;
-
-private void MoveDownRight ()
-{
- if (_cursor + 1 < _radioLabels.Count)
- {
- _cursor++;
- SetNeedsDisplay ();
- }
- else if (_cursor > 0)
- {
- _cursor = 0;
- SetNeedsDisplay ();
- }
-}
-
-private void MoveEnd () { _cursor = Math.Max (_radioLabels.Count - 1, 0); }
-private void MoveHome () { _cursor = 0; }
-
-private void MoveUpLeft ()
-{
- if (_cursor > 0)
- {
- _cursor--;
- SetNeedsDisplay ();
- }
- else if (_radioLabels.Count - 1 > 0)
- {
- _cursor = _radioLabels.Count - 1;
- SetNeedsDisplay ();
- }
-}
-
-private void RadioGroup_LayoutStarted (object sender, EventArgs e) { SetContentSize (); }
-private void SelectItem () { SelectedItem = _cursor; }
-
-private void SetContentSize ()
-{
- switch (_orientation)
- {
- case Orientation.Vertical:
- var width = 0;
-
- foreach (string s in _radioLabels)
- {
- width = Math.Max (s.GetColumns () + 2, width);
- }
-
- SetContentSize (new (width, _radioLabels.Count));
- break;
-
- case Orientation.Horizontal:
- _horizontal = new List<(int pos, int length)> ();
- var start = 0;
- var length = 0;
-
- for (var i = 0; i < _radioLabels.Count; i++)
- {
- start += length;
-
- length = _radioLabels [i].GetColumns () + 2 + (i < _radioLabels.Count - 1 ? _horizontalSpace : 0);
- _horizontal.Add ((start, length));
- }
- SetContentSize (new (_horizontal.Sum (item => item.length), 1));
- break;
- }
-}
}
diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs
index d336724a4..9ef64407f 100644
--- a/Terminal.Gui/Views/ScrollBarView.cs
+++ b/Terminal.Gui/Views/ScrollBarView.cs
@@ -33,8 +33,7 @@ public class ScrollBarView : View
private bool _vertical;
///
- /// Initializes a new instance of the class using
- /// layout.
+ /// Initializes a new instance of the class.
///
public ScrollBarView ()
{
@@ -46,8 +45,7 @@ public class ScrollBarView : View
}
///
- /// Initializes a new instance of the class using
- /// layout.
+ /// Initializes a new instance of the class.
///
/// The view that will host this scrollbar.
/// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs
index d62729223..42a226544 100644
--- a/Terminal.Gui/Views/ScrollView.cs
+++ b/Terminal.Gui/Views/ScrollView.cs
@@ -21,7 +21,7 @@ namespace Terminal.Gui;
///
/// The subviews that are added to this are offset by the
/// property. The view itself is a window into the space represented by the
-/// .
+/// .
///
/// Use the
///
@@ -38,8 +38,7 @@ public class ScrollView : View
private bool _showVerticalScrollIndicator;
///
- /// Initializes a new instance of the class using
- /// positioning.
+ /// Initializes a new instance of the class.
///
public ScrollView ()
{
@@ -88,10 +87,10 @@ public class ScrollView : View
AddCommand (Command.PageDown, () => ScrollDown (Viewport.Height));
AddCommand (Command.PageLeft, () => ScrollLeft (Viewport.Width));
AddCommand (Command.PageRight, () => ScrollRight (Viewport.Width));
- AddCommand (Command.TopHome, () => ScrollUp (ContentSize.Height));
- AddCommand (Command.BottomEnd, () => ScrollDown (ContentSize.Height));
- AddCommand (Command.LeftHome, () => ScrollLeft (ContentSize.Width));
- AddCommand (Command.RightEnd, () => ScrollRight (ContentSize.Width));
+ AddCommand (Command.TopHome, () => ScrollUp (GetContentSize ().Height));
+ AddCommand (Command.BottomEnd, () => ScrollDown (GetContentSize ().Height));
+ AddCommand (Command.LeftHome, () => ScrollLeft (GetContentSize ().Width));
+ AddCommand (Command.RightEnd, () => ScrollRight (GetContentSize ().Width));
// Default keybindings for this view
KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
@@ -127,7 +126,7 @@ public class ScrollView : View
}
SetContentOffset (_contentOffset);
- _contentView.Frame = new Rectangle (ContentOffset, ContentSize);
+ _contentView.Frame = new Rectangle (ContentOffset, GetContentSize ());
// PERF: How about calls to Point.Offset instead?
_vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); };
@@ -220,12 +219,12 @@ public class ScrollView : View
// get => ContentSize;
// set
// {
- // if (ContentSize != value)
+ // if (GetContentSize () != value)
// {
// ContentSize = value;
// _contentView.Frame = new Rectangle (_contentOffset, value);
- // _vertical.Size = ContentSize.Height;
- // _horizontal.Size = ContentSize.Width;
+ // _vertical.Size = GetContentSize ().Height;
+ // _horizontal.Size = GetContentSize ().Width;
// SetNeedsDisplay ();
// }
// }
@@ -244,26 +243,26 @@ public class ScrollView : View
_horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
Point p = default;
- if (value && -_contentOffset.X + Viewport.Width > ContentSize.Width)
+ if (value && -_contentOffset.X + Viewport.Width > GetContentSize ().Width)
{
p = new Point (
- ContentSize.Width - Viewport.Width + (_showVerticalScrollIndicator ? 1 : 0),
+ GetContentSize ().Width - Viewport.Width + (_showVerticalScrollIndicator ? 1 : 0),
-_contentOffset.Y
);
}
- if (value && -_contentOffset.Y + Viewport.Height > ContentSize.Height)
+ if (value && -_contentOffset.Y + Viewport.Height > GetContentSize ().Height)
{
if (p == default (Point))
{
p = new Point (
-_contentOffset.X,
- ContentSize.Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0)
+ GetContentSize ().Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0)
);
}
else
{
- p.Y = ContentSize.Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0);
+ p.Y = GetContentSize ().Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0);
}
}
@@ -607,7 +606,7 @@ public class ScrollView : View
{
// INTENT: Unclear intent. How about a call to Offset?
_contentOffset = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y));
- _contentView.Frame = new Rectangle (_contentOffset, ContentSize);
+ _contentView.Frame = new Rectangle (_contentOffset, GetContentSize ());
int p = Math.Max (0, -_contentOffset.Y);
if (_vertical.Position != p)
@@ -638,7 +637,7 @@ public class ScrollView : View
bool v = false, h = false;
var p = false;
- if (ContentSize is { } && (Viewport.Height == 0 || Viewport.Height > ContentSize.Height))
+ if (GetContentSize () is { } && (Viewport.Height == 0 || Viewport.Height > GetContentSize ().Height))
{
if (ShowVerticalScrollIndicator)
{
@@ -647,7 +646,7 @@ public class ScrollView : View
v = false;
}
- else if (ContentSize is { } && Viewport.Height > 0 && Viewport.Height == ContentSize.Height)
+ else if (GetContentSize () is { } && Viewport.Height > 0 && Viewport.Height == GetContentSize ().Height)
{
p = true;
}
@@ -661,7 +660,7 @@ public class ScrollView : View
v = true;
}
- if (ContentSize is { } && (Viewport.Width == 0 || Viewport.Width > ContentSize.Width))
+ if (GetContentSize () is { } && (Viewport.Width == 0 || Viewport.Width > GetContentSize ().Width))
{
if (ShowHorizontalScrollIndicator)
{
@@ -670,7 +669,7 @@ public class ScrollView : View
h = false;
}
- else if (ContentSize is { } && Viewport.Width > 0 && Viewport.Width == ContentSize.Width && p)
+ else if (GetContentSize () is { } && Viewport.Width > 0 && Viewport.Width == GetContentSize ().Width && p)
{
if (ShowHorizontalScrollIndicator)
{
diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs
index 992bc72fa..c42247299 100644
--- a/Terminal.Gui/Views/Slider.cs
+++ b/Terminal.Gui/Views/Slider.cs
@@ -1,210 +1,5 @@
namespace Terminal.Gui;
-/// for events.
-public class SliderOptionEventArgs : EventArgs
-{
- /// Initializes a new instance of
- /// indicates whether the option is set
- public SliderOptionEventArgs (bool isSet) { IsSet = isSet; }
-
- /// Gets whether the option is set or not.
- public bool IsSet { get; }
-}
-
-/// Represents an option in a .
-/// Data type of the option.
-public class SliderOption
-{
- /// Creates a new empty instance of the class.
- public SliderOption () { }
-
- /// Creates a new instance of the class with values for each property.
- public SliderOption (string legend, Rune legendAbbr, T data)
- {
- Legend = legend;
- LegendAbbr = legendAbbr;
- Data = data;
- }
-
- /// Event fired when the an option has changed.
- public event EventHandler Changed;
-
- /// Custom data of the option.
- public T Data { get; set; }
-
- /// Legend of the option.
- public string Legend { get; set; }
-
- ///
- /// Abbreviation of the Legend. When the too small to fit
- /// .
- ///
- public Rune LegendAbbr { get; set; }
-
- /// Event Raised when this option is set.
- public event EventHandler Set;
-
- /// Creates a human-readable string that represents this .
- public override string ToString () { return "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}"; }
-
- /// Event Raised when this option is unset.
- public event EventHandler UnSet;
-
- /// To Raise the event from the Slider.
- internal void OnChanged (bool isSet) { Changed?.Invoke (this, new (isSet)); }
-
- /// To Raise the event from the Slider.
- internal void OnSet () { Set?.Invoke (this, new (true)); }
-
- /// To Raise the event from the Slider.
- internal void OnUnSet () { UnSet?.Invoke (this, new (false)); }
-}
-
-/// Types
-public enum SliderType
-{
- ///
- ///
- /// ├─┼─┼─┼─┼─█─┼─┼─┼─┼─┼─┼─┤
- ///
- ///
- Single,
-
- ///
- ///
- /// ├─┼─█─┼─┼─█─┼─┼─┼─┼─█─┼─┤
- ///
- ///
- Multiple,
-
- ///
- ///
- /// ├▒▒▒▒▒▒▒▒▒█─┼─┼─┼─┼─┼─┼─┤
- ///
- ///
- LeftRange,
-
- ///
- ///
- /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒▒▒▒▒▒▒┤
- ///
- ///
- RightRange,
-
- ///
- ///
- /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒█─┼─┼─┤
- ///
- ///
- Range
-}
-
-/// Legend Style
-public class SliderAttributes
-{
- /// Attribute for the Legends Container.
- public Attribute? EmptyAttribute { get; set; }
-
- /// Attribute for when the respective Option is NOT Set.
- public Attribute? NormalAttribute { get; set; }
-
- /// Attribute for when the respective Option is Set.
- public Attribute? SetAttribute { get; set; }
-}
-
-/// Style
-public class SliderStyle
-{
- /// Constructs a new instance.
- public SliderStyle () { LegendAttributes = new (); }
-
- /// The glyph and the attribute to indicate mouse dragging.
- public Cell DragChar { get; set; }
-
- /// The glyph and the attribute used for empty spaces on the slider.
- public Cell EmptyChar { get; set; }
-
- /// The glyph and the attribute used for the end of ranges on the slider.
- public Cell EndRangeChar { get; set; }
-
- /// Legend attributes
- public SliderAttributes LegendAttributes { get; set; }
-
- /// The glyph and the attribute used for each option (tick) on the slider.
- public Cell OptionChar { get; set; }
-
- /// The glyph and the attribute used for filling in ranges on the slider.
- public Cell RangeChar { get; set; }
-
- /// The glyph and the attribute used for options (ticks) that are set on the slider.
- public Cell SetChar { get; set; }
-
- /// The glyph and the attribute used for spaces between options (ticks) on the slider.
- public Cell SpaceChar { get; set; }
-
- /// The glyph and the attribute used for the start of ranges on the slider.
- public Cell StartRangeChar { get; set; }
-}
-
-/// All configuration are grouped in this class.
-internal class SliderConfiguration
-{
- internal bool _allowEmpty;
- internal int _endSpacing;
- internal int _minInnerSpacing = 1;
- internal int _cachedInnerSpacing; // Currently calculated
- internal Orientation _legendsOrientation = Orientation.Horizontal;
- internal bool _rangeAllowSingle;
- internal bool _showEndSpacing;
- internal bool _showLegends;
- internal bool _showLegendsAbbr;
- internal Orientation _sliderOrientation = Orientation.Horizontal;
- internal int _startSpacing;
- internal SliderType _type = SliderType.Single;
- internal bool _useMinimumSize;
-}
-
-/// for events.
-public class SliderEventArgs : EventArgs
-{
- /// Initializes a new instance of
- /// The current options.
- /// Index of the option that is focused. -1 if no option has the focus.
- public SliderEventArgs (Dictionary> options, int focused = -1)
- {
- Options = options;
- Focused = focused;
- Cancel = false;
- }
-
- /// If set to true, the focus operation will be canceled, if applicable.
- public bool Cancel { get; set; }
-
- /// Gets or sets the index of the option that is focused.
- public int Focused { get; set; }
-
- /// Gets/sets whether the option is set or not.
- public Dictionary> Options { get; set; }
-}
-
-/// for events.
-public class OrientationEventArgs : EventArgs
-{
- /// Constructs a new instance.
- /// the new orientation
- public OrientationEventArgs (Orientation orientation)
- {
- Orientation = orientation;
- Cancel = false;
- }
-
- /// If set to true, the orientation change operation will be canceled, if applicable.
- public bool Cancel { get; set; }
-
- /// The new orientation.
- public Orientation Orientation { get; set; }
-}
-
/// Slider control.
public class Slider : Slider
{
@@ -1002,7 +797,7 @@ public class Slider : View
}
}
- private string AlignText (string text, int width, TextAlignment textAlignment)
+ private string AlignText (string text, int width, Alignment alignment)
{
if (text is null)
{
@@ -1019,20 +814,20 @@ public class Slider : View
string s2 = new (' ', w % 2);
// Note: The formatter doesn't handle all of this ???
- switch (textAlignment)
+ switch (alignment)
{
- case TextAlignment.Justified:
+ case Alignment.Fill:
return TextFormatter.Justify (text, width);
- case TextAlignment.Left:
+ case Alignment.Start:
return text + s1 + s1 + s2;
- case TextAlignment.Centered:
+ case Alignment.Center:
if (text.Length % 2 != 0)
{
return s1 + text + s1 + s2;
}
return s1 + s2 + text + s1;
- case TextAlignment.Right:
+ case Alignment.End:
return s1 + s1 + s2 + text;
default:
return text;
@@ -1139,12 +934,6 @@ public class Slider : View
}
break;
- case SliderType.Single:
- break;
- case SliderType.Multiple:
- break;
- default:
- throw new ArgumentOutOfRangeException ();
}
}
@@ -1365,7 +1154,7 @@ public class Slider : View
switch (_config._legendsOrientation)
{
case Orientation.Horizontal:
- text = AlignText (text, _config._cachedInnerSpacing + 1, TextAlignment.Centered);
+ text = AlignText (text, _config._cachedInnerSpacing + 1, Alignment.Center);
break;
case Orientation.Vertical:
@@ -1383,7 +1172,7 @@ public class Slider : View
break;
case Orientation.Vertical:
- text = AlignText (text, _config._cachedInnerSpacing + 1, TextAlignment.Centered);
+ text = AlignText (text, _config._cachedInnerSpacing + 1, Alignment.Center);
break;
}
diff --git a/Terminal.Gui/Views/SliderAttributes.cs b/Terminal.Gui/Views/SliderAttributes.cs
new file mode 100644
index 000000000..6f75546dd
--- /dev/null
+++ b/Terminal.Gui/Views/SliderAttributes.cs
@@ -0,0 +1,14 @@
+namespace Terminal.Gui;
+
+/// Legend Style
+public class SliderAttributes
+{
+ /// Attribute for the Legends Container.
+ public Attribute? EmptyAttribute { get; set; }
+
+ /// Attribute for when the respective Option is NOT Set.
+ public Attribute? NormalAttribute { get; set; }
+
+ /// Attribute for when the respective Option is Set.
+ public Attribute? SetAttribute { get; set; }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/SliderConfiguration.cs b/Terminal.Gui/Views/SliderConfiguration.cs
new file mode 100644
index 000000000..3cadafd86
--- /dev/null
+++ b/Terminal.Gui/Views/SliderConfiguration.cs
@@ -0,0 +1,19 @@
+namespace Terminal.Gui;
+
+/// All configuration are grouped in this class.
+internal class SliderConfiguration
+{
+ internal bool _allowEmpty;
+ internal int _endSpacing;
+ internal int _minInnerSpacing = 1;
+ internal int _cachedInnerSpacing; // Currently calculated
+ internal Orientation _legendsOrientation = Orientation.Horizontal;
+ internal bool _rangeAllowSingle;
+ internal bool _showEndSpacing;
+ internal bool _showLegends;
+ internal bool _showLegendsAbbr;
+ internal Orientation _sliderOrientation = Orientation.Horizontal;
+ internal int _startSpacing;
+ internal SliderType _type = SliderType.Single;
+ internal bool _useMinimumSize;
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/SliderEventArgs.cs b/Terminal.Gui/Views/SliderEventArgs.cs
new file mode 100644
index 000000000..76c4eed90
--- /dev/null
+++ b/Terminal.Gui/Views/SliderEventArgs.cs
@@ -0,0 +1,24 @@
+namespace Terminal.Gui;
+
+/// for events.
+public class SliderEventArgs : EventArgs
+{
+ /// Initializes a new instance of
+ /// The current options.
+ /// Index of the option that is focused. -1 if no option has the focus.
+ public SliderEventArgs (Dictionary> options, int focused = -1)
+ {
+ Options = options;
+ Focused = focused;
+ Cancel = false;
+ }
+
+ /// If set to true, the focus operation will be canceled, if applicable.
+ public bool Cancel { get; set; }
+
+ /// Gets or sets the index of the option that is focused.
+ public int Focused { get; set; }
+
+ /// Gets/sets whether the option is set or not.
+ public Dictionary> Options { get; set; }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/SliderOption.cs b/Terminal.Gui/Views/SliderOption.cs
new file mode 100644
index 000000000..1cfcc1f07
--- /dev/null
+++ b/Terminal.Gui/Views/SliderOption.cs
@@ -0,0 +1,50 @@
+namespace Terminal.Gui;
+
+/// Represents an option in a .
+/// Data type of the option.
+public class SliderOption
+{
+ /// Creates a new empty instance of the class.
+ public SliderOption () { }
+
+ /// Creates a new instance of the class with values for each property.
+ public SliderOption (string legend, Rune legendAbbr, T data)
+ {
+ Legend = legend;
+ LegendAbbr = legendAbbr;
+ Data = data;
+ }
+
+ /// Event fired when the an option has changed.
+ public event EventHandler Changed;
+
+ /// Custom data of the option.
+ public T Data { get; set; }
+
+ /// Legend of the option.
+ public string Legend { get; set; }
+
+ ///
+ /// Abbreviation of the Legend. When the too small to fit
+ /// .
+ ///
+ public Rune LegendAbbr { get; set; }
+
+ /// Event Raised when this option is set.
+ public event EventHandler Set;
+
+ /// Creates a human-readable string that represents this .
+ public override string ToString () { return "{Legend=" + Legend + ", LegendAbbr=" + LegendAbbr + ", Data=" + Data + "}"; }
+
+ /// Event Raised when this option is unset.
+ public event EventHandler UnSet;
+
+ /// To Raise the event from the Slider.
+ internal void OnChanged (bool isSet) { Changed?.Invoke (this, new (isSet)); }
+
+ /// To Raise the event from the Slider.
+ internal void OnSet () { Set?.Invoke (this, new (true)); }
+
+ /// To Raise the event from the Slider.
+ internal void OnUnSet () { UnSet?.Invoke (this, new (false)); }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/SliderOptionEventArgs.cs b/Terminal.Gui/Views/SliderOptionEventArgs.cs
new file mode 100644
index 000000000..b4b5e6936
--- /dev/null
+++ b/Terminal.Gui/Views/SliderOptionEventArgs.cs
@@ -0,0 +1,12 @@
+namespace Terminal.Gui;
+
+/// for events.
+public class SliderOptionEventArgs : EventArgs
+{
+ /// Initializes a new instance of
+ /// indicates whether the option is set
+ public SliderOptionEventArgs (bool isSet) { IsSet = isSet; }
+
+ /// Gets whether the option is set or not.
+ public bool IsSet { get; }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/SliderStyle.cs b/Terminal.Gui/Views/SliderStyle.cs
new file mode 100644
index 000000000..e6429d6cb
--- /dev/null
+++ b/Terminal.Gui/Views/SliderStyle.cs
@@ -0,0 +1,35 @@
+namespace Terminal.Gui;
+
+/// Style
+public class SliderStyle
+{
+ /// Constructs a new instance.
+ public SliderStyle () { LegendAttributes = new (); }
+
+ /// The glyph and the attribute to indicate mouse dragging.
+ public Cell DragChar { get; set; }
+
+ /// The glyph and the attribute used for empty spaces on the slider.
+ public Cell EmptyChar { get; set; }
+
+ /// The glyph and the attribute used for the end of ranges on the slider.
+ public Cell EndRangeChar { get; set; }
+
+ /// Legend attributes
+ public SliderAttributes LegendAttributes { get; set; }
+
+ /// The glyph and the attribute used for each option (tick) on the slider.
+ public Cell OptionChar { get; set; }
+
+ /// The glyph and the attribute used for filling in ranges on the slider.
+ public Cell RangeChar { get; set; }
+
+ /// The glyph and the attribute used for options (ticks) that are set on the slider.
+ public Cell SetChar { get; set; }
+
+ /// The glyph and the attribute used for spaces between options (ticks) on the slider.
+ public Cell SpaceChar { get; set; }
+
+ /// The glyph and the attribute used for the start of ranges on the slider.
+ public Cell StartRangeChar { get; set; }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/SliderType.cs b/Terminal.Gui/Views/SliderType.cs
new file mode 100644
index 000000000..7cbde908a
--- /dev/null
+++ b/Terminal.Gui/Views/SliderType.cs
@@ -0,0 +1,40 @@
+namespace Terminal.Gui;
+
+/// Types
+public enum SliderType
+{
+ ///
+ ///
+ /// ├─┼─┼─┼─┼─█─┼─┼─┼─┼─┼─┼─┤
+ ///
+ ///
+ Single,
+
+ ///
+ ///
+ /// ├─┼─█─┼─┼─█─┼─┼─┼─┼─█─┼─┤
+ ///
+ ///
+ Multiple,
+
+ ///
+ ///
+ /// ├▒▒▒▒▒▒▒▒▒█─┼─┼─┼─┼─┼─┼─┤
+ ///
+ ///
+ LeftRange,
+
+ ///
+ ///
+ /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒▒▒▒▒▒▒┤
+ ///
+ ///
+ RightRange,
+
+ ///
+ ///
+ /// ├─┼─┼─┼─┼─█▒▒▒▒▒▒▒█─┼─┼─┤
+ ///
+ ///
+ Range
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs
index 71208e034..4694eebfb 100644
--- a/Terminal.Gui/Views/StatusBar.cs
+++ b/Terminal.Gui/Views/StatusBar.cs
@@ -1,63 +1,5 @@
namespace Terminal.Gui;
-///
-/// objects are contained by s. Each
-/// has a title, a shortcut (hotkey), and an that will be invoked when
-/// the is pressed. The will be a global hotkey for
-/// the application in the current context of the screen. The color of the will be
-/// changed after each ~. A set to `~F1~ Help` will render as *F1* using
-/// and *Help* as .
-///
-public class StatusItem
-{
- /// Initializes a new .
- /// Shortcut to activate the .
- /// Title for the .
- /// Action to invoke when the is activated.
- /// Function to determine if the action can currently be executed.
- public StatusItem (Key shortcut, string title, Action action, Func canExecute = null)
- {
- Title = title ?? "";
- Shortcut = shortcut;
- Action = action;
- CanExecute = canExecute;
- }
-
- /// Gets or sets the action to be invoked when the statusbar item is triggered
- /// Action to invoke.
- public Action Action { get; set; }
-
- ///
- /// Gets or sets the action to be invoked to determine if the can be triggered. If
- /// returns the status item will be enabled. Otherwise, it will be
- /// disabled.
- ///
- /// Function to determine if the action is can be executed or not.
- public Func CanExecute { get; set; }
-
- /// Gets or sets arbitrary data for the status item.
- /// This property is not used internally.
- public object Data { get; set; }
-
- /// Gets the global shortcut to invoke the action on the menu.
- public Key Shortcut { get; set; }
-
- /// Gets or sets the title.
- /// The title.
- ///
- /// The colour of the will be changed after each ~. A
- /// set to `~F1~ Help` will render as *F1* using and
- /// *Help* as .
- ///
- public string Title { get; set; }
-
- ///
- /// Returns if the status item is enabled. This method is a wrapper around
- /// .
- ///
- public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
-}
-
///
/// A status bar is a that snaps to the bottom of a displaying set of
/// s. The should be context sensitive. This means, if the main menu
@@ -69,8 +11,7 @@ public class StatusBar : View
{
private static Rune _shortcutDelimiter = (Rune)'=';
- private StatusItem [] _items = { };
- private StatusItem _itemToInvoke;
+ private StatusItem [] _items = [];
/// Initializes a new instance of the class.
public StatusBar () : this (new StatusItem [] { }) { }
@@ -91,10 +32,11 @@ public class StatusBar : View
CanFocus = false;
ColorScheme = Colors.ColorSchemes ["Menu"];
X = 0;
- Y = Pos.AnchorEnd (1);
+ Y = Pos.AnchorEnd ();
Width = Dim.Fill ();
- Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == ContentSize
- AddCommand (Command.Accept, InvokeItem);
+ Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
+
+ AddCommand (Command.Accept, ctx => InvokeItem ((StatusItem)ctx.KeyBinding?.Context));
}
/// The items that compose the
@@ -110,9 +52,10 @@ public class StatusBar : View
_items = value;
- foreach (StatusItem item in _items)
+ foreach (StatusItem item in _items.Where (i => i.Shortcut != Key.Empty))
{
- KeyBindings.Add (item.Shortcut, KeyBindingScope.HotKey, Command.Accept);
+ KeyBinding keyBinding = new (new [] { Command.Accept }, KeyBindingScope.HotKey, item);
+ KeyBindings.Add (item.Shortcut, keyBinding);
}
}
}
@@ -142,7 +85,7 @@ public class StatusBar : View
}
///
- protected internal override bool OnMouseEvent (MouseEvent me)
+ protected internal override bool OnMouseEvent (MouseEvent me)
{
if (me.Flags != MouseFlags.Button1Clicked)
{
@@ -215,32 +158,6 @@ public class StatusBar : View
}
}
- ///
- public override bool? OnInvokingKeyBindings (Key keyEvent)
- {
- // This is a bit of a hack. We want to handle the key bindings for status bar but
- // InvokeKeyBindings doesn't pass any context so we can't tell which item it is for.
- // So before we call the base class we set SelectedItem appropriately.
- Key key = new (keyEvent);
-
- if (KeyBindings.TryGet (key, out _))
- {
- // Search RadioLabels
- foreach (StatusItem item in Items)
- {
- if (item.Shortcut == key)
- {
- _itemToInvoke = item;
- //keyEvent.Scope = KeyBindingScope.HotKey;
-
- break;
- }
- }
- }
-
- return base.OnInvokingKeyBindings (keyEvent);
- }
-
/// Removes a at specified index of .
/// The zero-based index of the item to remove.
/// The removed.
@@ -287,11 +204,11 @@ public class StatusBar : View
return len;
}
- private bool? InvokeItem ()
+ private bool? InvokeItem (StatusItem itemToInvoke)
{
- if (_itemToInvoke is { Action: { } })
+ if (itemToInvoke is { Action: { } })
{
- _itemToInvoke.Action.Invoke ();
+ itemToInvoke.Action.Invoke ();
return true;
}
diff --git a/Terminal.Gui/Views/StatusItem.cs b/Terminal.Gui/Views/StatusItem.cs
new file mode 100644
index 000000000..5028f722e
--- /dev/null
+++ b/Terminal.Gui/Views/StatusItem.cs
@@ -0,0 +1,59 @@
+namespace Terminal.Gui;
+
+///
+/// objects are contained by s. Each
+/// has a title, a shortcut (hotkey), and an that will be invoked when
+/// the is pressed. The will be a global hotkey for
+/// the application in the current context of the screen. The color of the will be
+/// changed after each ~. A set to `~F1~ Help` will render as *F1* using
+/// and *Help* as .
+///
+public class StatusItem
+{
+ /// Initializes a new .
+ /// Shortcut to activate the .
+ /// Title for the .
+ /// Action to invoke when the is activated.
+ /// Function to determine if the action can currently be executed.
+ public StatusItem (Key shortcut, string title, Action action, Func canExecute = null)
+ {
+ Title = title ?? "";
+ Shortcut = shortcut;
+ Action = action;
+ CanExecute = canExecute;
+ }
+
+ /// Gets or sets the action to be invoked when the is triggered
+ /// Action to invoke.
+ public Action Action { get; set; }
+
+ ///
+ /// Gets or sets the action to be invoked to determine if the can be triggered. If
+ /// returns the status item will be enabled. Otherwise, it will be
+ /// disabled.
+ ///
+ /// Function to determine if the action is can be executed or not.
+ public Func CanExecute { get; set; }
+
+ /// Gets or sets arbitrary data for the status item.
+ /// This property is not used internally.
+ public object Data { get; set; }
+
+ /// Gets the global shortcut to invoke the action on the menu.
+ public Key Shortcut { get; set; }
+
+ /// Gets or sets the title.
+ /// The title.
+ ///
+ /// The colour of the will be changed after each ~. A
+ /// set to `~F1~ Help` will render as *F1* using and
+ /// *Help* as .
+ ///
+ public string Title { get; set; }
+
+ ///
+ /// Returns if the status item is enabled. This method is a wrapper around
+ /// .
+ ///
+ public bool IsEnabled () { return CanExecute?.Invoke () ?? true; }
+}
diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs
index 52036e1e9..095b73582 100644
--- a/Terminal.Gui/Views/TabView.cs
+++ b/Terminal.Gui/Views/TabView.cs
@@ -21,7 +21,7 @@ public class TabView : View
private TabToRender [] _tabLocations;
private int _tabScrollOffset;
- /// Initializes a class using layout.
+ /// Initializes a class.
public TabView ()
{
CanFocus = true;
@@ -564,7 +564,7 @@ public class TabView : View
_host = host;
CanFocus = true;
- Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == ContentSize
+ Height = 1; // BUGBUG: Views should avoid setting Height as doing so implies Frame.Size == GetContentSize ().
Width = Dim.Fill ();
_rightScrollIndicator = new View
diff --git a/Terminal.Gui/Views/TableView/ColumnStyle.cs b/Terminal.Gui/Views/TableView/ColumnStyle.cs
index cbbc2a3ac..2d277abd9 100644
--- a/Terminal.Gui/Views/TableView/ColumnStyle.cs
+++ b/Terminal.Gui/Views/TableView/ColumnStyle.cs
@@ -8,10 +8,10 @@
public class ColumnStyle
{
///
- /// Defines a delegate for returning custom alignment per cell based on cell values. When specified this will
+ /// Defines a delegate for returning custom alignment per cell based on cell values. When specified this will
/// override
///
- public Func AlignmentGetter;
+ public Func AlignmentGetter;
///
/// Defines a delegate for returning a custom color scheme per cell based on cell values. Return null for the
@@ -20,26 +20,26 @@ public class ColumnStyle
public CellColorGetterDelegate ColorGetter;
///
- /// Defines a delegate for returning custom representations of cell values. If not set then
- /// is used. Return values from your delegate may be truncated e.g. based on
+ /// Defines a delegate for returning custom representations of cell values. If not set then
+ /// is used. Return values from your delegate may be truncated e.g. based on
///
///
public Func RepresentationGetter;
- private bool visible = true;
+ private bool _visible = true;
///
- /// Defines the default alignment for all values rendered in this column. For custom alignment based on cell
+ /// Defines the default alignment for all values rendered in this column. For custom alignment based on cell
/// contents use .
///
- public TextAlignment Alignment { get; set; }
+ public Alignment Alignment { get; set; }
/// Defines the format for values e.g. "yyyy-MM-dd" for dates
public string Format { get; set; }
///
- /// Set the maximum width of the column in characters. This value will be ignored if more than the tables
- /// . Defaults to
+ /// Set the maximum width of the column in characters. This value will be ignored if more than the tables
+ /// . Defaults to
///
public int MaxWidth { get; set; } = TableView.DefaultMaxCellWidth;
@@ -47,7 +47,7 @@ public class ColumnStyle
public int MinAcceptableWidth { get; set; } = TableView.DefaultMinAcceptableWidth;
///
- /// Set the minimum width of the column in characters. Setting this will ensure that even when a column has short
+ /// Set the minimum width of the column in characters. Setting this will ensure that even when a column has short
/// content/header it still fills a given width of the control.
///
/// This value will be ignored if more than the tables or the
@@ -64,8 +64,8 @@ public class ColumnStyle
/// If is 0 then will always return false.
public bool Visible
{
- get => MaxWidth >= 0 && visible;
- set => visible = value;
+ get => MaxWidth >= 0 && _visible;
+ set => _visible = value;
}
///
@@ -74,7 +74,7 @@ public class ColumnStyle
///
///
///
- public TextAlignment GetAlignment (object cellValue)
+ public Alignment GetAlignment (object cellValue)
{
if (AlignmentGetter is { })
{
diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs
index 2cf258bee..4dd947734 100644
--- a/Terminal.Gui/Views/TableView/TableStyle.cs
+++ b/Terminal.Gui/Views/TableView/TableStyle.cs
@@ -15,11 +15,11 @@ public class TableStyle
///
public bool AlwaysUseNormalColorForVerticalCellLines { get; set; } = false;
- /// Collection of columns for which you want special rendering (e.g. custom column lengths, text alignment etc)
+ /// Collection of columns for which you want special rendering (e.g. custom column lengths, text justification, etc.)
public Dictionary ColumnStyles { get; set; } = new ();
///
- /// Determines rendering when the last column in the table is visible but it's content or
+ /// Determines rendering when the last column in the table is visible, but it's content or
/// is less than the remaining space in the control. True (the default) will expand
/// the column to fill the remaining bounds of the control. False will draw a column ending line and leave a blank
/// column that cannot be selected in the remaining space.
diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs
index 0c39895e3..7081106a4 100644
--- a/Terminal.Gui/Views/TableView/TableView.cs
+++ b/Terminal.Gui/Views/TableView/TableView.cs
@@ -37,12 +37,12 @@ public class TableView : View
private TableStyle style = new ();
private ITableSource table;
- /// Initializes a class using layout.
+ /// Initializes a class.
/// The table to display in the control
public TableView (ITableSource table) : this () { Table = table; }
///
- /// Initializes a class using layout. Set the
+ /// Initializes a class. Set the
/// property to begin editing
///
public TableView ()
@@ -908,10 +908,10 @@ public class TableView : View
// What columns to render at what X offset in viewport
ColumnToRender [] columnsToRender = CalculateViewport (Viewport).ToArray ();
- Driver.SetAttribute (GetNormalColor ());
+ Driver?.SetAttribute (GetNormalColor ());
//invalidate current row (prevents scrolling around leaving old characters in the frame
- Driver.AddStr (new string (' ', Viewport.Width));
+ Driver?.AddStr (new string (' ', Viewport.Width));
var line = 0;
@@ -2014,7 +2014,7 @@ public class TableView : View
///
/// Returns true if the is not set or all the columns in the have an
- /// explicit that marks them .
+ /// explicit that marks them .
///
///
private bool TableIsNullOrInvisible ()
@@ -2116,16 +2116,16 @@ public class TableView : View
- (representation.EnumerateRunes ().Sum (c => c.GetColumns ())
+ 1 /*leave 1 space for cell boundary*/);
- switch (colStyle?.GetAlignment (originalCellValue) ?? TextAlignment.Left)
+ switch (colStyle?.GetAlignment (originalCellValue) ?? Alignment.Start)
{
- case TextAlignment.Left:
+ case Alignment.Start:
return representation + new string (' ', toPad);
- case TextAlignment.Right:
+ case Alignment.End:
return new string (' ', toPad) + representation;
// TODO: With single line cells, centered and justified are the same right?
- case TextAlignment.Centered:
- case TextAlignment.Justified:
+ case Alignment.Center:
+ case Alignment.Fill:
return
new string (' ', (int)Math.Floor (toPad / 2.0))
+ // round down
diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs
index b651703f3..3323e4541 100644
--- a/Terminal.Gui/Views/TextField.cs
+++ b/Terminal.Gui/Views/TextField.cs
@@ -21,8 +21,7 @@ public class TextField : View
private List _text;
///
- /// Initializes a new instance of the class using
- /// positioning.
+ /// Initializes a new instance of the class.
///
public TextField ()
{
diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs
index 268bf0c73..0e36a80c2 100644
--- a/Terminal.Gui/Views/TextValidateField.cs
+++ b/Terminal.Gui/Views/TextValidateField.cs
@@ -392,8 +392,7 @@ namespace Terminal.Gui
private ITextValidateProvider _provider;
///
- /// Initializes a new instance of the class using
- /// positioning.
+ /// Initializes a new instance of the class.
///
public TextValidateField ()
{
@@ -539,7 +538,7 @@ namespace Terminal.Gui
{
int c = _provider.Cursor (mouseEvent.Position.X - GetMargins (Viewport.Width).left);
- if (_provider.Fixed == false && TextAlignment == TextAlignment.Right && Text.Length > 0)
+ if (_provider.Fixed == false && TextAlignment == Alignment.End && Text.Length > 0)
{
c++;
}
@@ -633,7 +632,7 @@ namespace Terminal.Gui
// When it's right-aligned and it's a normal input, the cursor behaves differently.
int curPos;
- if (_provider?.Fixed == false && TextAlignment == TextAlignment.Right)
+ if (_provider?.Fixed == false && TextAlignment == Alignment.End)
{
curPos = _cursorPosition + left - 1;
}
@@ -650,7 +649,7 @@ namespace Terminal.Gui
///
private bool BackspaceKeyHandler ()
{
- if (_provider.Fixed == false && TextAlignment == TextAlignment.Right && _cursorPosition <= 1)
+ if (_provider.Fixed == false && TextAlignment == Alignment.End && _cursorPosition <= 1)
{
return false;
}
@@ -688,7 +687,7 @@ namespace Terminal.Gui
///
private bool DeleteKeyHandler ()
{
- if (_provider.Fixed == false && TextAlignment == TextAlignment.Right)
+ if (_provider.Fixed == false && TextAlignment == Alignment.End)
{
_cursorPosition = _provider.CursorLeft (_cursorPosition);
}
@@ -719,11 +718,11 @@ namespace Terminal.Gui
switch (TextAlignment)
{
- case TextAlignment.Left:
+ case Alignment.Start:
return (0, total);
- case TextAlignment.Centered:
+ case Alignment.Center:
return (total / 2, total / 2 + total % 2);
- case TextAlignment.Right:
+ case Alignment.End:
return (total, 0);
default:
return (0, total);
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index 7a70a94d0..8b4f17424 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -1784,7 +1784,7 @@ internal class WordWrapManager
TextFormatter.Format (
TextModel.ToString (line),
width,
- TextAlignment.Left,
+ Alignment.Start,
true,
preserveTrailingSpaces,
tabWidth
@@ -2588,25 +2588,6 @@ public class TextView : View
///
public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete ();
- ///
- /// The bottom offset needed to use a horizontal scrollbar or for another reason. This is only needed with the
- /// keyboard navigation.
- ///
- public int BottomOffset
- {
- get => _bottomOffset;
- set
- {
- if (CurrentRow == Lines - 1 && _bottomOffset > 0 && value == 0)
- {
- _topRow = Math.Max (_topRow - _bottomOffset, 0);
- }
-
- _bottomOffset = value;
- Adjust ();
- }
- }
-
/// Get the for this view.
public ContextMenu? ContextMenu { get; }
@@ -2678,7 +2659,7 @@ public class TextView : View
public int Lines => _model.Count;
/// Gets the maximum visible length line.
- public int Maxlength => _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height, TabWidth);
+ public int Maxlength => _model.GetMaxVisibleLine (_topRow, _topRow + Viewport.Height, TabWidth);
/// Gets or sets a value indicating whether this is a multiline text view.
public bool Multiline
@@ -2736,30 +2717,12 @@ public class TextView : View
_isReadOnly = value;
SetNeedsDisplay ();
+ WrapTextModel ();
Adjust ();
}
}
}
- ///
- /// The right offset needed to use a vertical scrollbar or for another reason. This is only needed with the
- /// keyboard navigation.
- ///
- public int RightOffset
- {
- get => _rightOffset;
- set
- {
- if (!_wordWrap && CurrentColumn == GetCurrentLine ().Count && _rightOffset > 0 && value == 0)
- {
- _leftColumn = Math.Max (_leftColumn - _rightOffset, 0);
- }
-
- _rightOffset = value;
- Adjust ();
- }
- }
-
/// Length of the selected text.
public int SelectedLength => GetSelectedLength ();
@@ -2852,7 +2815,7 @@ public class TextView : View
if (_wordWrap)
{
_wrapManager = new (_model);
- _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _);
+ _model = _wrapManager.WrapModel (Viewport.Width, out _, out _, out _, out _);
}
OnTextChanged (old, Text);
@@ -2897,7 +2860,7 @@ public class TextView : View
if (_wordWrap)
{
_wrapManager = new (_model);
- _model = _wrapManager.WrapModel (_frameWidth, out _, out _, out _, out _);
+ WrapTextModel ();
}
else if (!_wordWrap && _wrapManager is { })
{
@@ -2908,7 +2871,6 @@ public class TextView : View
}
}
- private int _frameWidth => Math.Max (Frame.Width - (RightOffset != 0 ? 2 : 1), 0);
/// Allows clearing the items updating the original text.
public void ClearHistoryChanges () { _historyText?.Clear (Text); }
@@ -3389,34 +3351,34 @@ public class TextView : View
if (_model.Count > 0 && _shiftSelecting && Selecting)
{
- if (CurrentRow - _topRow + BottomOffset >= Frame.Height - 1 && _model.Count + BottomOffset > _topRow + CurrentRow)
+ if (CurrentRow - _topRow >= Viewport.Height - 1 && _model.Count > _topRow + CurrentRow)
{
- ScrollTo (_topRow + Frame.Height);
+ ScrollTo (_topRow + Viewport.Height);
}
else if (_topRow > 0 && CurrentRow <= _topRow)
{
- ScrollTo (_topRow - Frame.Height);
+ ScrollTo (_topRow - Viewport.Height);
}
- else if (ev.Position.Y >= Frame.Height)
+ else if (ev.Position.Y >= Viewport.Height)
{
- ScrollTo (_model.Count + BottomOffset);
+ ScrollTo (_model.Count);
}
else if (ev.Position.Y < 0 && _topRow > 0)
{
ScrollTo (0);
}
- if (CurrentColumn - _leftColumn + RightOffset >= Frame.Width - 1 && line.Count + RightOffset > _leftColumn + CurrentColumn)
+ if (CurrentColumn - _leftColumn >= Viewport.Width - 1 && line.Count > _leftColumn + CurrentColumn)
{
- ScrollTo (_leftColumn + Frame.Width, false);
+ ScrollTo (_leftColumn + Viewport.Width, false);
}
else if (_leftColumn > 0 && CurrentColumn <= _leftColumn)
{
- ScrollTo (_leftColumn - Frame.Width, false);
+ ScrollTo (_leftColumn - Viewport.Width, false);
}
- else if (ev.Position.X >= Frame.Width)
+ else if (ev.Position.X >= Viewport.Width)
{
- ScrollTo (line.Count + RightOffset, false);
+ ScrollTo (line.Count, false);
}
else if (ev.Position.X < 0 && _leftColumn > 0)
{
@@ -3585,8 +3547,8 @@ public class TextView : View
SetNormalColor ();
(int width, int height) offB = OffSetBackground ();
- int right = Frame.Width + offB.width + RightOffset;
- int bottom = Frame.Height + offB.height + BottomOffset;
+ int right = Viewport.Width + offB.width;
+ int bottom = Viewport.Height + offB.height;
var row = 0;
for (int idxRow = _topRow; idxRow < _model.Count; idxRow++)
@@ -3826,9 +3788,9 @@ public class TextView : View
if (Application.MouseGrabView == this && Selecting)
{
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
- //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Frame.Height);
- //var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height);
- //SetNeedsDisplay (new (0, minRow, Frame.Width, maxRow));
+ //var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
+ //var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
+ //SetNeedsDisplay (new (0, minRow, Viewport.Width, maxRow));
SetNeedsDisplay ();
}
@@ -3851,7 +3813,7 @@ public class TextView : View
cols += TabWidth + 1;
}
- if (!TextModel.SetCol (ref col, Frame.Width, cols))
+ if (!TextModel.SetCol (ref col, Viewport.Width, cols))
{
col = CurrentColumn;
@@ -3863,7 +3825,7 @@ public class TextView : View
int posX = CurrentColumn - _leftColumn;
int posY = CurrentRow - _topRow;
- if (posX > -1 && col >= posX && posX < Frame.Width - RightOffset && _topRow <= CurrentRow && posY < Frame.Height - BottomOffset)
+ if (posX > -1 && col >= posX && posX < Viewport.Width && _topRow <= CurrentRow && posY < Viewport.Height)
{
Move (col, CurrentRow - _topRow);
return new (col, CurrentRow - _topRow);
@@ -3934,7 +3896,7 @@ public class TextView : View
else if (!_wordWrap)
{
int maxlength =
- _model.GetMaxVisibleLine (_topRow, _topRow + Frame.Height + RightOffset, TabWidth);
+ _model.GetMaxVisibleLine (_topRow, _topRow + Viewport.Height, TabWidth);
_leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0);
}
@@ -4113,18 +4075,18 @@ public class TextView : View
need = true;
}
else if (!_wordWrap
- && (CurrentColumn - _leftColumn + RightOffset > Frame.Width + offB.width || dSize.size + RightOffset >= Frame.Width + offB.width))
+ && (CurrentColumn - _leftColumn + 1 > Viewport.Width + offB.width || dSize.size + 1 >= Viewport.Width + offB.width))
{
_leftColumn = TextModel.CalculateLeftColumn (
line,
_leftColumn,
CurrentColumn,
- Frame.Width + offB.width - RightOffset,
+ Viewport.Width + offB.width,
TabWidth
);
need = true;
}
- else if ((_wordWrap && _leftColumn > 0) || (dSize.size + RightOffset < Frame.Width + offB.width && tSize.size + RightOffset < Frame.Width + offB.width))
+ else if ((_wordWrap && _leftColumn > 0) || (dSize.size < Viewport.Width + offB.width && tSize.size < Viewport.Width + offB.width))
{
if (_leftColumn > 0)
{
@@ -4138,9 +4100,9 @@ public class TextView : View
_topRow = CurrentRow;
need = true;
}
- else if (CurrentRow - _topRow + BottomOffset >= Frame.Height + offB.height)
+ else if (CurrentRow - _topRow >= Viewport.Height + offB.height)
{
- _topRow = Math.Min (Math.Max (CurrentRow - Frame.Height + 1 + BottomOffset, 0), CurrentRow);
+ _topRow = Math.Min (Math.Max (CurrentRow - Viewport.Height + 1, 0), CurrentRow);
need = true;
}
else if (_topRow > 0 && CurrentRow < _topRow)
@@ -4161,7 +4123,10 @@ public class TextView : View
}
else
{
- PositionCursor ();
+ if (IsInitialized)
+ {
+ PositionCursor ();
+ }
}
OnUnwrappedCursorPosition ();
@@ -4282,7 +4247,7 @@ public class TextView : View
{
//QUESTION: Is the below comment still relevant?
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
- //SetNeedsDisplay (new (0, startRow - topRow, Frame.Width, startRow - topRow + 1));
+ //SetNeedsDisplay (new (0, startRow - topRow, Viewport.Width, startRow - topRow + 1));
SetNeedsDisplay ();
}
@@ -4378,7 +4343,7 @@ public class TextView : View
else
{
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
- //SetNeedsDisplay (new (0, currentRow - topRow, 1, Frame.Width));
+ //SetNeedsDisplay (new (0, currentRow - topRow, 1, Viewport.Width));
SetNeedsDisplay ();
}
}
@@ -4470,7 +4435,7 @@ public class TextView : View
_wrapNeeded = true;
}
- DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Frame.Width, CurrentRow - _topRow + 1));
+ DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, CurrentRow - _topRow + 1));
}
else
{
@@ -4493,8 +4458,8 @@ public class TextView : View
new (
CurrentColumn - _leftColumn,
CurrentRow - _topRow,
- Frame.Width,
- CurrentRow - _topRow + 1
+ Viewport.Width,
+ Math.Max (CurrentRow - _topRow + 1, 0)
)
);
}
@@ -4788,7 +4753,7 @@ public class TextView : View
if (!_wrapNeeded)
{
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
- //SetNeedsDisplay (new (0, prow, Math.Max (Frame.Width, 0), Math.Max (prow + 1, 0)));
+ //SetNeedsDisplay (new (0, prow, Math.Max (Viewport.Width, 0), Math.Max (prow + 1, 0)));
SetNeedsDisplay ();
}
}
@@ -4825,9 +4790,9 @@ public class TextView : View
HistoryText.LineStatus.Replaced
);
- if (!_wordWrap && CurrentColumn - _leftColumn > Frame.Width)
+ if (!_wordWrap && CurrentColumn - _leftColumn > Viewport.Width)
{
- _leftColumn = Math.Max (CurrentColumn - Frame.Width + 1, 0);
+ _leftColumn = Math.Max (CurrentColumn - Viewport.Width + 1, 0);
}
if (_wordWrap)
@@ -4837,7 +4802,7 @@ public class TextView : View
else
{
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
- //SetNeedsDisplay (new (0, currentRow - topRow, Frame.Width, Math.Max (currentRow - topRow + 1, 0)));
+ //SetNeedsDisplay (new (0, currentRow - topRow, Viewport.Width, Math.Max (currentRow - topRow + 1, 0)));
SetNeedsDisplay ();
}
@@ -4933,7 +4898,7 @@ public class TextView : View
Insert (new () { Rune = a.AsRune, ColorScheme = colorScheme });
CurrentColumn++;
- if (CurrentColumn >= _leftColumn + Frame.Width)
+ if (CurrentColumn >= _leftColumn + Viewport.Width)
{
_leftColumn++;
SetNeedsDisplay ();
@@ -5051,7 +5016,7 @@ public class TextView : View
UpdateWrapModel ();
- DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Frame.Width, Frame.Height));
+ DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
_lastWasKill = setLastWasKill;
DoNeededAction ();
@@ -5158,7 +5123,7 @@ public class TextView : View
UpdateWrapModel ();
- DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Frame.Width, Frame.Height));
+ DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
_lastWasKill = setLastWasKill;
DoNeededAction ();
@@ -5228,7 +5193,7 @@ public class TextView : View
UpdateWrapModel ();
- DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Frame.Width, Frame.Height));
+ DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
DoNeededAction ();
}
@@ -5287,7 +5252,7 @@ public class TextView : View
UpdateWrapModel ();
- DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Frame.Width, Frame.Height));
+ DoSetNeedsDisplay (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
DoNeededAction ();
}
@@ -5303,7 +5268,7 @@ public class TextView : View
if (!_multiline && !IsInitialized)
{
CurrentColumn = Text.GetRuneCount ();
- _leftColumn = CurrentColumn > Frame.Width + 1 ? CurrentColumn - Frame.Width + 1 : 0;
+ _leftColumn = CurrentColumn > Viewport.Width + 1 ? CurrentColumn - Viewport.Width + 1 : 0;
}
}
@@ -5337,7 +5302,7 @@ public class TextView : View
CurrentRow++;
- if (CurrentRow + BottomOffset >= _topRow + Frame.Height)
+ if (CurrentRow >= _topRow + Viewport.Height)
{
_topRow++;
SetNeedsDisplay ();
@@ -5346,7 +5311,7 @@ public class TextView : View
TrackColumn ();
PositionCursor ();
}
- else if (CurrentRow > Frame.Height)
+ else if (CurrentRow > Viewport.Height)
{
Adjust ();
}
@@ -5357,7 +5322,7 @@ public class TextView : View
private void MoveEndOfLine ()
{
List currentLine = GetCurrentLine ();
- CurrentColumn = currentLine.Count;
+ CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0);
Adjust ();
DoNeededAction ();
}
@@ -5381,7 +5346,7 @@ public class TextView : View
}
List currentLine = GetCurrentLine ();
- CurrentColumn = currentLine.Count;
+ CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0);
}
}
@@ -5401,7 +5366,7 @@ public class TextView : View
private void MovePageDown ()
{
- int nPageDnShift = Frame.Height - 1;
+ int nPageDnShift = Viewport.Height - 1;
if (CurrentRow >= 0 && CurrentRow < _model.Count)
{
@@ -5431,7 +5396,7 @@ public class TextView : View
private void MovePageUp ()
{
- int nPageUpShift = Frame.Height - 1;
+ int nPageUpShift = Viewport.Height - 1;
if (CurrentRow > 0)
{
@@ -5469,7 +5434,7 @@ public class TextView : View
{
List currentLine = GetCurrentLine ();
- if (CurrentColumn < currentLine.Count)
+ if ((ReadOnly ? CurrentColumn + 1 : CurrentColumn) < currentLine.Count)
{
CurrentColumn++;
}
@@ -5480,7 +5445,7 @@ public class TextView : View
CurrentRow++;
CurrentColumn = 0;
- if (CurrentRow >= _topRow + Frame.Height)
+ if (CurrentRow >= _topRow + Viewport.Height)
{
_topRow++;
SetNeedsDisplay ();
@@ -5581,14 +5546,14 @@ public class TextView : View
var w = 0;
var h = 0;
- if (SuperView?.Frame.Right - Frame.Right < 0)
+ if (SuperView?.Viewport.Right - Viewport.Right < 0)
{
- w = SuperView!.Frame.Right - Frame.Right - 1;
+ w = SuperView!.Viewport.Right - Viewport.Right - 1;
}
- if (SuperView?.Frame.Bottom - Frame.Bottom < 0)
+ if (SuperView?.Viewport.Bottom - Viewport.Bottom < 0)
{
- h = SuperView!.Frame.Bottom - Frame.Bottom - 1;
+ h = SuperView!.Viewport.Bottom - Viewport.Bottom - 1;
}
return (w, h);
@@ -5835,9 +5800,9 @@ public class TextView : View
r = GetCurrentLine ();
int idx = TextModel.GetColFromX (r, _leftColumn, Math.Max (ev.Position.X, 0), TabWidth);
- if (idx - _leftColumn >= r.Count + RightOffset)
+ if (idx - _leftColumn >= r.Count)
{
- CurrentColumn = Math.Max (r.Count - _leftColumn + RightOffset, 0);
+ CurrentColumn = Math.Max (r.Count - _leftColumn - (ReadOnly ? 1 : 0), 0);
}
else
{
@@ -6123,7 +6088,7 @@ public class TextView : View
var fullNeedsDisplay = false;
- if (CurrentRow >= _topRow + Frame.Height)
+ if (CurrentRow >= _topRow + Viewport.Height)
{
_topRow++;
fullNeedsDisplay = true;
@@ -6150,7 +6115,7 @@ public class TextView : View
else
{
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
- //SetNeedsDisplay (new (0, currentRow - topRow, 2, Frame.Height));
+ //SetNeedsDisplay (new (0, currentRow - topRow, 2, Viewport.Height));
SetNeedsDisplay ();
}
@@ -6481,7 +6446,7 @@ public class TextView : View
if (_wordWrap && _wrapManager is { })
{
_model = _wrapManager.WrapModel (
- _frameWidth,
+ Math.Max (Viewport.Width - (ReadOnly ? 0 : 1), 0), // For the cursor on the last column of a line
out int nRow,
out int nCol,
out int nStartRow,
diff --git a/Terminal.Gui/Views/TimeField.cs b/Terminal.Gui/Views/TimeField.cs
index 4550992c4..d1101c243 100644
--- a/Terminal.Gui/Views/TimeField.cs
+++ b/Terminal.Gui/Views/TimeField.cs
@@ -21,7 +21,7 @@ public class TimeField : TextField
private bool _isShort;
private TimeSpan _time;
- /// Initializes a new instance of using positioning.
+ /// Initializes a new instance of .
public TimeField ()
{
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs
index 0b5bd14ad..117599efb 100644
--- a/Terminal.Gui/Views/Toplevel.cs
+++ b/Terminal.Gui/Views/Toplevel.cs
@@ -1,5 +1,3 @@
-using System.Net.Mime;
-
namespace Terminal.Gui;
///
@@ -23,7 +21,7 @@ namespace Terminal.Gui;
public partial class Toplevel : View
{
///
- /// Initializes a new instance of the class with layout,
+ /// Initializes a new instance of the class,
/// defaulting to full screen. The and properties will be set to the
/// dimensions of the terminal using .
///
@@ -108,7 +106,7 @@ public partial class Toplevel : View
);
// Default keybindings for this view
- KeyBindings.Add (Application.QuitKey, Command.QuitToplevel);
+ KeyBindings.Add (Application.QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
KeyBindings.Add (Key.CursorRight, Command.NextView);
KeyBindings.Add (Key.CursorDown, Command.NextView);
@@ -120,12 +118,17 @@ public partial class Toplevel : View
KeyBindings.Add (Key.Tab.WithCtrl, Command.NextViewOrTop);
KeyBindings.Add (Key.Tab.WithShift.WithCtrl, Command.PreviousViewOrTop);
- KeyBindings.Add (Key.F5, Command.Refresh);
+ // TODO: Refresh Key should be configurable
+ KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
KeyBindings.Add (Application.AlternateForwardKey, Command.NextViewOrTop); // Needed on Unix
KeyBindings.Add (Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
+ if (Environment.OSVersion.Platform == PlatformID.Unix)
+ {
+ KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
+ }
+
#if UNIX_KEY_BINDINGS
- KeyBindings.Add (Key.Z.WithCtrl, Command.Suspend);
KeyBindings.Add (Key.L.WithCtrl, Command.Refresh); // Unix
KeyBindings.Add (Key.F.WithCtrl, Command.NextView); // Unix
KeyBindings.Add (Key.I.WithCtrl, Command.NextView); // Unix
@@ -397,9 +400,7 @@ public partial class Toplevel : View
}
if ((superView != top || top?.SuperView is { } || (top != Application.Top && top.Modal) || (top?.SuperView is null && top.IsOverlapped))
-
- // BUGBUG: Prevously PositionToplevel required LayotuStyle.Computed
- && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) /*&& top.LayoutStyle == LayoutStyle.Computed*/)
+ && (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y))
{
if ((top.X is null || top.X is PosAbsolute) && top.Frame.X != nx)
{
diff --git a/Terminal.Gui/Views/Window.cs b/Terminal.Gui/Views/Window.cs
index 8c2c6738b..0a1dfe32e 100644
--- a/Terminal.Gui/Views/Window.cs
+++ b/Terminal.Gui/Views/Window.cs
@@ -15,8 +15,7 @@ namespace Terminal.Gui;
public class Window : Toplevel
{
///
- /// Initializes a new instance of the class using
- /// positioning.
+ /// Initializes a new instance of the class.
///
public Window ()
{
diff --git a/Terminal.Gui/Views/Wizard/Wizard.cs b/Terminal.Gui/Views/Wizard/Wizard.cs
index 205739d44..ab81e515a 100644
--- a/Terminal.Gui/Views/Wizard/Wizard.cs
+++ b/Terminal.Gui/Views/Wizard/Wizard.cs
@@ -54,28 +54,10 @@ public class Wizard : Dialog
private readonly LinkedList _steps = new ();
private WizardStep _currentStep;
private bool _finishedPressed;
-
- /////
- ///// The title of the Wizard, shown at the top of the Wizard with " - currentStep.Title" appended.
- /////
- /////
- ///// The Title is only displayed when the is set to false.
- /////
- //public new string Title {
- // get {
- // // The base (Dialog) Title holds the full title ("Wizard Title - Step Title")
- // return base.Title;
- // }
- // set {
- // wizardTitle = value;
- // base.Title = $"{wizardTitle}{(steps.Count > 0 && currentStep is { } ? " - " + currentStep.Title : string.Empty)}";
- // }
- //}
private string _wizardTitle = string.Empty;
///
- /// Initializes a new instance of the class using
- /// positioning.
+ /// Initializes a new instance of the class.
///
///
/// The Wizard will be vertically and horizontally centered in the container. After initialization use X,
@@ -83,9 +65,9 @@ public class Wizard : Dialog
///
public Wizard ()
{
- // Using Justify causes the Back and Next buttons to be hard justified against
- // the left and right edge
- ButtonAlignment = ButtonAlignments.Justify;
+ // TODO: LastEndRestStart will enable a "Quit" button to always appear at the far left
+ ButtonAlignment = Alignment.Start;
+ ButtonAlignmentModes |= AlignmentModes.IgnoreFirstOrLast;
BorderStyle = LineStyle.Double;
//// Add a horiz separator
diff --git a/Terminal.Gui/Views/Wizard/WizardStep.cs b/Terminal.Gui/Views/Wizard/WizardStep.cs
index 490307ee0..c28c3653c 100644
--- a/Terminal.Gui/Views/Wizard/WizardStep.cs
+++ b/Terminal.Gui/Views/Wizard/WizardStep.cs
@@ -40,8 +40,7 @@ public class WizardStep : FrameView
private readonly TextView _helpTextView = new ();
///
- /// Initializes a new instance of the class using
- /// positioning.
+ /// Initializes a new instance of the class.
///
public WizardStep ()
{
diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings
index ca15b5583..8ca3af1c0 100644
--- a/Terminal.sln.DotSettings
+++ b/Terminal.sln.DotSettings
@@ -391,6 +391,7 @@
<Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy><Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy><Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy>
+ PushToShowHintsTrueTrueTrue
@@ -439,5 +440,6 @@
Concurrency Issue(?<=\W|^)(?<TAG>CONCURRENCY:)(\W|$)(.*)Warning
+ TrueTrue
diff --git a/UICatalog/KeyBindingsDialog.cs b/UICatalog/KeyBindingsDialog.cs
index 4e2e760dc..cff34b4c1 100644
--- a/UICatalog/KeyBindingsDialog.cs
+++ b/UICatalog/KeyBindingsDialog.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using Terminal.Gui;
@@ -10,7 +11,7 @@ internal class KeyBindingsDialog : Dialog
// TODO: Update to use Key instead of KeyCode
private static readonly Dictionary CurrentBindings = new ();
- private readonly Command [] _commands;
+ private readonly ObservableCollection _commands;
private readonly ListView _commandsListView;
private readonly Label _keyLabel;
@@ -26,13 +27,13 @@ internal class KeyBindingsDialog : Dialog
}
// known commands that views can support
- _commands = Enum.GetValues (typeof (Command)).Cast ().ToArray ();
+ _commands = new (Enum.GetValues (typeof (Command)).Cast ().ToArray ());
_commandsListView = new ListView
{
Width = Dim.Percent (50),
Height = Dim.Percent (100) - 1,
- Source = new ListWrapper (_commands),
+ Source = new ListWrapper (_commands),
SelectedItem = 0
};
diff --git a/UICatalog/Resources/config.json b/UICatalog/Resources/config.json
index 4e651245c..2accf1b4d 100644
--- a/UICatalog/Resources/config.json
+++ b/UICatalog/Resources/config.json
@@ -32,6 +32,7 @@
"Themes": [
{
"UI Catalog Theme": {
+ "Dialog.DefaultButtonAlignment": "Fill",
"ColorSchemes": [
{
"UI Catalog Scheme": {
diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs
index e0eace794..47467caba 100644
--- a/UICatalog/Scenario.cs
+++ b/UICatalog/Scenario.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using Terminal.Gui;
@@ -97,7 +98,7 @@ public class Scenario : IDisposable
/// .
/// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
///
- public static List GetScenarios ()
+ public static ObservableCollection GetScenarios ()
{
List objects = new ();
@@ -113,7 +114,7 @@ public class Scenario : IDisposable
_maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
}
- return objects.OrderBy (s => s.GetName ()).ToList ();
+ return new (objects.OrderBy (s => s.GetName ()).ToList ());
}
///
@@ -239,24 +240,26 @@ public class Scenario : IDisposable
#endregion IDispose
/// Returns a list of all Categories set by all of the s defined in the project.
- internal static List GetAllCategories ()
+ internal static ObservableCollection GetAllCategories ()
{
- List categories = new ();
+ List aCategories = [];
- categories = typeof (Scenario).Assembly.GetTypes ()
- .Where (
- myType => myType.IsClass
- && !myType.IsAbstract
- && myType.IsSubclassOf (typeof (Scenario)))
- .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
- .Aggregate (
- categories,
- (current, attrs) => current
- .Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name))
- .ToList ());
+ aCategories = typeof (Scenario).Assembly.GetTypes ()
+ .Where (
+ myType => myType.IsClass
+ && !myType.IsAbstract
+ && myType.IsSubclassOf (typeof (Scenario)))
+ .Select (type => System.Attribute.GetCustomAttributes (type).ToList ())
+ .Aggregate (
+ aCategories,
+ (current, attrs) => current
+ .Union (
+ attrs.Where (a => a is ScenarioCategory)
+ .Select (a => ((ScenarioCategory)a).Name))
+ .ToList ());
// Sort
- categories = categories.OrderBy (c => c).ToList ();
+ ObservableCollection categories = new (aCategories.OrderBy (c => c).ToList ());
// Put "All" at the top
categories.Insert (0, "All Scenarios");
@@ -264,7 +267,7 @@ public class Scenario : IDisposable
return categories;
}
- /// Defines the category names used to catagorize a
+ /// Defines the category names used to categorize a
[AttributeUsage (AttributeTargets.Class, AllowMultiple = true)]
public class ScenarioCategory (string Name) : System.Attribute
{
diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs
index f1e57c253..5eb586aa1 100644
--- a/UICatalog/Scenarios/ASCIICustomButton.cs
+++ b/UICatalog/Scenarios/ASCIICustomButton.cs
@@ -235,6 +235,7 @@ public class ASCIICustomButtonTest : Scenario
pages++;
}
+ // BUGBUG: set_ContentSize is supposed to be `protected`.
_scrollView.SetContentSize (new (25, pages * BUTTONS_ON_PAGE * BUTTON_HEIGHT));
if (_smallerWindow)
@@ -269,7 +270,7 @@ public class ASCIICustomButtonTest : Scenario
case KeyCode.End:
_scrollView.ContentOffset = new Point (
_scrollView.ContentOffset.X,
- -(_scrollView.ContentSize.Height
+ -(_scrollView.GetContentSize ().Height
- _scrollView.Frame.Height
+ (_scrollView.ShowHorizontalScrollIndicator ? 1 : 0))
);
@@ -287,7 +288,7 @@ public class ASCIICustomButtonTest : Scenario
Math.Max (
_scrollView.ContentOffset.Y
- _scrollView.Frame.Height,
- -(_scrollView.ContentSize.Height
+ -(_scrollView.GetContentSize ().Height
- _scrollView.Frame.Height
+ (_scrollView.ShowHorizontalScrollIndicator
? 1
diff --git a/UICatalog/Scenarios/AdornmentEditor.cs b/UICatalog/Scenarios/AdornmentEditor.cs
new file mode 100644
index 000000000..a83aef990
--- /dev/null
+++ b/UICatalog/Scenarios/AdornmentEditor.cs
@@ -0,0 +1,236 @@
+using System;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+///
+/// Provides a composable UI for editing the settings of an Adornment.
+///
+public class AdornmentEditor : View
+{
+ private readonly ColorPicker _backgroundColorPicker = new ()
+ {
+ Title = "_BG",
+ BoxWidth = 1,
+ BoxHeight = 1,
+ BorderStyle = LineStyle.Single,
+ SuperViewRendersLineCanvas = true,
+ Enabled = false
+ };
+
+ private readonly ColorPicker _foregroundColorPicker = new ()
+ {
+ Title = "_FG",
+ BoxWidth = 1,
+ BoxHeight = 1,
+ BorderStyle = LineStyle.Single,
+ SuperViewRendersLineCanvas = true,
+ Enabled = false
+ };
+
+ private Adornment _adornment;
+ public Adornment AdornmentToEdit
+ {
+ get => _adornment;
+ set
+ {
+ if (value == _adornment)
+ {
+ return;
+ }
+
+ _adornment = value;
+
+ foreach (var subview in Subviews)
+ {
+ subview.Enabled = _adornment is { };
+ }
+
+ if (_adornment is null)
+ {
+ return;
+ }
+
+ if (IsInitialized)
+ {
+ _topEdit.Value = _adornment.Thickness.Top;
+ _leftEdit.Value = _adornment.Thickness.Left;
+ _bottomEdit.Value = _adornment.Thickness.Bottom;
+ _rightEdit.Value = _adornment.Thickness.Right;
+
+ _adornment.Initialized += (sender, args) =>
+ {
+ var cs = _adornment.ColorScheme;
+ _foregroundColorPicker.SelectedColor = cs.Normal.Foreground.GetClosestNamedColor ();
+ _backgroundColorPicker.SelectedColor = cs.Normal.Background.GetClosestNamedColor ();
+
+ };
+ }
+
+ OnAdornmentChanged ();
+ }
+ }
+
+ public event EventHandler AdornmentChanged;
+
+ public void OnAdornmentChanged ()
+ {
+ AdornmentChanged?.Invoke (this, EventArgs.Empty);
+ }
+
+ private Buttons.NumericUpDown _topEdit;
+ private Buttons.NumericUpDown _leftEdit;
+ private Buttons.NumericUpDown _bottomEdit;
+ private Buttons.NumericUpDown _rightEdit;
+
+ public AdornmentEditor ()
+ {
+ Width = Dim.Auto (DimAutoStyle.Content);
+ Height = Dim.Auto (DimAutoStyle.Content);
+
+ BorderStyle = LineStyle.Dashed;
+ Initialized += AdornmentEditor_Initialized;
+ }
+
+ private void AdornmentEditor_Initialized (object sender, EventArgs e)
+ {
+ ExpanderButton expandButton;
+ Border.Add (expandButton = new ExpanderButton ());
+
+ _topEdit = new ()
+ {
+ X = Pos.Center (), Y = 0,
+ Enabled = false
+ };
+
+ _topEdit.ValueChanging += Top_ValueChanging;
+ Add (_topEdit);
+
+ _leftEdit = new ()
+ {
+ X = Pos.Left (_topEdit) - Pos.Func (() => _topEdit.Digits) - 2, Y = Pos.Bottom (_topEdit),
+ Enabled = false
+ };
+
+ _leftEdit.ValueChanging += Left_ValueChanging;
+ Add (_leftEdit);
+
+ _rightEdit = new ()
+ {
+ X = Pos.Right (_leftEdit) + 5, Y = Pos.Bottom (_topEdit),
+ Enabled = false
+ };
+
+ _rightEdit.ValueChanging += Right_ValueChanging;
+ Add (_rightEdit);
+
+ _bottomEdit = new ()
+ {
+ X = Pos.Center (), Y = Pos.Bottom (_leftEdit),
+ Enabled = false
+ };
+
+ _bottomEdit.ValueChanging += Bottom_ValueChanging;
+ Add (_bottomEdit);
+
+ var copyTop = new Button
+ {
+ X = Pos.Center (), Y = Pos.Bottom (_bottomEdit), Text = "Cop_y Top",
+ Enabled = false
+ };
+
+ copyTop.Accept += (s, e) =>
+ {
+ AdornmentToEdit.Thickness = new (_topEdit.Value);
+ _leftEdit.Value = _rightEdit.Value = _bottomEdit.Value = _topEdit.Value;
+ };
+ Add (copyTop);
+
+ // Foreground ColorPicker.
+ _foregroundColorPicker.X = 0;
+ _foregroundColorPicker.Y = Pos.Bottom (copyTop);
+
+ _foregroundColorPicker.ColorChanged += ColorPickerColorChanged ();
+ Add (_foregroundColorPicker);
+
+ // Background ColorPicker.
+ _backgroundColorPicker.X = Pos.Right (_foregroundColorPicker) - 1;
+ _backgroundColorPicker.Y = Pos.Top (_foregroundColorPicker);
+
+ _backgroundColorPicker.ColorChanged += ColorPickerColorChanged ();
+ Add (_backgroundColorPicker);
+
+ _topEdit.Value = AdornmentToEdit?.Thickness.Top ?? 0;
+ _leftEdit.Value = AdornmentToEdit?.Thickness.Left ?? 0;
+ _rightEdit.Value = AdornmentToEdit?.Thickness.Right ?? 0;
+ _bottomEdit.Value = AdornmentToEdit?.Thickness.Bottom ?? 0;
+
+ foreach (var subview in Subviews)
+ {
+ subview.Enabled = AdornmentToEdit is { };
+ }
+ }
+
+ private EventHandler ColorPickerColorChanged ()
+ {
+ return (o, a) =>
+ {
+ if (AdornmentToEdit is null)
+ {
+ return;
+ }
+ AdornmentToEdit.ColorScheme = new (AdornmentToEdit.ColorScheme)
+ {
+ Normal = new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor)
+ };
+ };
+ }
+
+ private void Top_ValueChanging (object sender, StateEventArgs e)
+ {
+ if (e.NewValue < 0 || AdornmentToEdit is null)
+ {
+ e.Cancel = true;
+
+ return;
+ }
+
+ AdornmentToEdit.Thickness = new (AdornmentToEdit.Thickness.Left, e.NewValue, AdornmentToEdit.Thickness.Right, AdornmentToEdit.Thickness.Bottom);
+ }
+
+ private void Left_ValueChanging (object sender, StateEventArgs e)
+ {
+ if (e.NewValue < 0 || AdornmentToEdit is null)
+ {
+ e.Cancel = true;
+
+ return;
+ }
+
+ AdornmentToEdit.Thickness = new (e.NewValue, AdornmentToEdit.Thickness.Top, AdornmentToEdit.Thickness.Right, AdornmentToEdit.Thickness.Bottom);
+ }
+
+ private void Right_ValueChanging (object sender, StateEventArgs e)
+ {
+ if (e.NewValue < 0 || AdornmentToEdit is null)
+ {
+ e.Cancel = true;
+
+ return;
+ }
+
+ AdornmentToEdit.Thickness = new (AdornmentToEdit.Thickness.Left, AdornmentToEdit.Thickness.Top, e.NewValue, AdornmentToEdit.Thickness.Bottom);
+ }
+
+ private void Bottom_ValueChanging (object sender, StateEventArgs e)
+ {
+ if (e.NewValue < 0 || AdornmentToEdit is null)
+ {
+ e.Cancel = true;
+
+ return;
+ }
+
+ AdornmentToEdit.Thickness = new (AdornmentToEdit.Thickness.Left, AdornmentToEdit.Thickness.Top, AdornmentToEdit.Thickness.Right, e.NewValue);
+ }
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/Adornments.cs b/UICatalog/Scenarios/Adornments.cs
index c47f2af83..cde56a8c7 100644
--- a/UICatalog/Scenarios/Adornments.cs
+++ b/UICatalog/Scenarios/Adornments.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Terminal.Gui;
+using Terminal.Gui;
namespace UICatalog.Scenarios;
@@ -10,27 +7,32 @@ namespace UICatalog.Scenarios;
[ScenarioCategory ("Borders")]
public class Adornments : Scenario
{
- private ViewDiagnosticFlags _diagnosticFlags;
-
public override void Main ()
{
Application.Init ();
- _diagnosticFlags = View.Diagnostics;
-
Window app = new ()
{
Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
};
- var editor = new AdornmentsEditor ();
+ var editor = new AdornmentsEditor
+ {
+ AutoSelectViewToEdit = true,
+ // This is for giggles, to show that the editor can be moved around.
+ Arrangement = ViewArrangement.Movable,
+ X = Pos.AnchorEnd()
+
+ };
+ editor.Border.Thickness = new Thickness (1, 3, 1, 1);
+
app.Add (editor);
var window = new Window
{
Title = "The _Window",
Arrangement = ViewArrangement.Movable,
- X = Pos.Right (editor),
+ // X = Pos.Center (),
Width = Dim.Percent (60),
Height = Dim.Percent (80)
};
@@ -126,442 +128,9 @@ public class Adornments : Scenario
#endif
};
- app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags;
-
Application.Run (app);
app.Dispose ();
Application.Shutdown ();
}
-
- ///
- /// Provides a composable UI for editing the settings of an Adornment.
- ///
- public class AdornmentEditor : View
- {
- private readonly ColorPicker _backgroundColorPicker = new ()
- {
- Title = "_BG",
- BoxWidth = 1,
- BoxHeight = 1,
- BorderStyle = LineStyle.Single,
- SuperViewRendersLineCanvas = true
- };
-
- private readonly ColorPicker _foregroundColorPicker = new ()
- {
- Title = "_FG",
- BoxWidth = 1,
- BoxHeight = 1,
- BorderStyle = LineStyle.Single,
- SuperViewRendersLineCanvas = true
- };
-
- private Buttons.NumericUpDown _topEdit;
- private Buttons.NumericUpDown _leftEdit;
- private Buttons.NumericUpDown _bottomEdit;
- private Buttons.NumericUpDown _rightEdit;
- private Thickness _thickness;
- private bool _isUpdating;
-
- public AdornmentEditor ()
- {
- Margin.Thickness = new (0);
- BorderStyle = LineStyle.Double;
- Initialized += AdornmentEditor_Initialized;
- }
-
- public Attribute Color
- {
- get => new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor);
- set
- {
- _foregroundColorPicker.SelectedColor = value.Foreground.GetClosestNamedColor ();
- _backgroundColorPicker.SelectedColor = value.Background.GetClosestNamedColor ();
- }
- }
-
- public Thickness Thickness
- {
- get => _thickness;
- set
- {
- if (_isUpdating)
- {
- return;
- }
-
- _thickness = value;
- ThicknessChanged?.Invoke (this, new () { Thickness = Thickness });
-
- if (IsInitialized)
- {
- _isUpdating = true;
- _topEdit.Value = _thickness.Top;
- _leftEdit.Value = _thickness.Left;
- _rightEdit.Value = _thickness.Right;
- _bottomEdit.Value = _thickness.Bottom;
-
- _isUpdating = false;
- }
- }
- }
-
- public event EventHandler AttributeChanged;
- public event EventHandler ThicknessChanged;
-
- private void AdornmentEditor_Initialized (object sender, EventArgs e)
- {
- SuperViewRendersLineCanvas = true;
-
- _topEdit = new ()
- {
- X = Pos.Center (), Y = 0
- };
-
- _topEdit.ValueChanging += Top_ValueChanging;
- Add (_topEdit);
-
- _leftEdit = new ()
- {
- X = Pos.Left (_topEdit) - Pos.Func (() => _topEdit.Digits) - 2, Y = Pos.Bottom (_topEdit)
- };
-
- _leftEdit.ValueChanging += Left_ValueChanging;
- Add (_leftEdit);
-
- _rightEdit = new () { X = Pos.Right (_leftEdit) + 5, Y = Pos.Bottom (_topEdit) };
-
- _rightEdit.ValueChanging += Right_ValueChanging;
- Add (_rightEdit);
-
- _bottomEdit = new () { X = Pos.Center (), Y = Pos.Bottom (_leftEdit) };
-
- _bottomEdit.ValueChanging += Bottom_ValueChanging;
- Add (_bottomEdit);
-
- var copyTop = new Button { X = Pos.Center (), Y = Pos.Bottom (_bottomEdit), Text = "Cop_y Top" };
-
- copyTop.Accept += (s, e) =>
- {
- Thickness = new (Thickness.Top);
- _leftEdit.Value = _rightEdit.Value = _bottomEdit.Value = _topEdit.Value;
- };
- Add (copyTop);
-
- // Foreground ColorPicker.
- _foregroundColorPicker.X = -1;
- _foregroundColorPicker.Y = Pos.Bottom (copyTop);
- _foregroundColorPicker.SelectedColor = Color.Foreground.GetClosestNamedColor ();
-
- _foregroundColorPicker.ColorChanged += (o, a) =>
- AttributeChanged?.Invoke (
- this,
- new (
- _foregroundColorPicker.SelectedColor,
- _backgroundColorPicker.SelectedColor
- )
- );
- Add (_foregroundColorPicker);
-
- // Background ColorPicker.
- _backgroundColorPicker.X = Pos.Right (_foregroundColorPicker) - 1;
- _backgroundColorPicker.Y = Pos.Top (_foregroundColorPicker);
- _backgroundColorPicker.SelectedColor = Color.Background.GetClosestNamedColor ();
-
- _backgroundColorPicker.ColorChanged += (o, a) =>
- AttributeChanged?.Invoke (
- this,
- new (
- _foregroundColorPicker.SelectedColor,
- _backgroundColorPicker.SelectedColor
- )
- );
- Add (_backgroundColorPicker);
-
- _topEdit.Value = Thickness.Top;
- _leftEdit.Value = Thickness.Left;
- _rightEdit.Value = Thickness.Right;
- _bottomEdit.Value = Thickness.Bottom;
-
- Width = Dim.Auto () - 1;
- Height = Dim.Auto () - 1;
- LayoutSubviews ();
- }
-
- private void Top_ValueChanging (object sender, StateEventArgs e)
- {
- if (e.NewValue < 0)
- {
- e.Cancel = true;
-
- return;
- }
-
- Thickness.Top = e.NewValue;
- }
-
- private void Left_ValueChanging (object sender, StateEventArgs e)
- {
- if (e.NewValue < 0)
- {
- e.Cancel = true;
-
- return;
- }
-
- Thickness.Left = e.NewValue;
- }
-
- private void Right_ValueChanging (object sender, StateEventArgs e)
- {
- if (e.NewValue < 0)
- {
- e.Cancel = true;
-
- return;
- }
-
- Thickness.Right = e.NewValue;
- }
-
- private void Bottom_ValueChanging (object sender, StateEventArgs e)
- {
- if (e.NewValue < 0)
- {
- e.Cancel = true;
-
- return;
- }
-
- Thickness.Bottom = e.NewValue;
- }
- }
-
- ///
- /// Provides an editor UI for the Margin, Border, and Padding of a View.
- ///
- public class AdornmentsEditor : View
- {
- private AdornmentEditor _borderEditor;
- private CheckBox _diagCheckBox;
- private AdornmentEditor _marginEditor;
- private string _origTitle = string.Empty;
- private AdornmentEditor _paddingEditor;
- private View _viewToEdit;
-
- public AdornmentsEditor ()
- {
- ColorScheme = Colors.ColorSchemes ["Dialog"];
-
- // TOOD: Use Dim.Auto
- Width = 36;
- Height = Dim.Fill ();
- }
-
- public View ViewToEdit
- {
- get => _viewToEdit;
- set
- {
- _origTitle = value.Title;
- _viewToEdit = value;
-
- _marginEditor = new ()
- {
- X = 0,
- Y = 0,
- Title = "_Margin",
- Thickness = _viewToEdit.Margin.Thickness,
- Color = new (_viewToEdit.Margin.ColorScheme?.Normal ?? ColorScheme.Normal),
- SuperViewRendersLineCanvas = true
- };
- _marginEditor.ThicknessChanged += Editor_ThicknessChanged;
- _marginEditor.AttributeChanged += Editor_AttributeChanged;
- Add (_marginEditor);
-
- _borderEditor = new ()
- {
- X = Pos.Left (_marginEditor),
- Y = Pos.Bottom (_marginEditor),
- Title = "B_order",
- Thickness = _viewToEdit.Border.Thickness,
- Color = new (_viewToEdit.Border.ColorScheme?.Normal ?? ColorScheme.Normal),
- SuperViewRendersLineCanvas = true
- };
- _borderEditor.ThicknessChanged += Editor_ThicknessChanged;
- _borderEditor.AttributeChanged += Editor_AttributeChanged;
- Add (_borderEditor);
-
- List borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast ().ToList ();
-
- var rbBorderStyle = new RadioGroup
- {
- X = Pos.Right (_borderEditor) - 1,
- Y = Pos.Top (_borderEditor),
- SelectedItem = (int)_viewToEdit.Border.LineStyle,
- BorderStyle = LineStyle.Double,
- Title = "Border St_yle",
- SuperViewRendersLineCanvas = true,
- RadioLabels = borderStyleEnum.Select (
- e => e.ToString ()
- )
- .ToArray ()
- };
- Add (rbBorderStyle);
-
- rbBorderStyle.SelectedItemChanged += (s, e) =>
- {
- LineStyle prevBorderStyle = _viewToEdit.BorderStyle;
- _viewToEdit.Border.LineStyle = (LineStyle)e.SelectedItem;
-
- if (_viewToEdit.Border.LineStyle == LineStyle.None)
- {
- _viewToEdit.Border.Thickness = new (0);
- }
- else if (prevBorderStyle == LineStyle.None && _viewToEdit.Border.LineStyle != LineStyle.None)
- {
- _viewToEdit.Border.Thickness = new (1);
- }
-
- _borderEditor.Thickness = new (
- _viewToEdit.Border.Thickness.Left,
- _viewToEdit.Border.Thickness.Top,
- _viewToEdit.Border.Thickness.Right,
- _viewToEdit.Border.Thickness.Bottom
- );
- _viewToEdit.SetNeedsDisplay ();
- LayoutSubviews ();
- };
-
- var ckbTitle = new CheckBox
- {
- BorderStyle = LineStyle.Double,
- X = Pos.Left (_borderEditor),
- Y = Pos.Bottom (_borderEditor) - 1,
-
- //Width = Dim.Width (_borderEditor),
- Checked = true,
- SuperViewRendersLineCanvas = true,
- Text = "Show Title"
- };
-
- ckbTitle.Toggled += (sender, args) =>
- {
- if (ckbTitle.Checked == true)
- {
- //_viewToEdit.Title = _origTitle;
- }
- else
- {
- _viewToEdit.Title = string.Empty;
- }
- };
- Add (ckbTitle);
-
- _paddingEditor = new ()
- {
- X = Pos.Left (_borderEditor),
- Y = Pos.Bottom (rbBorderStyle),
- Title = "_Padding",
- Thickness = _viewToEdit.Padding.Thickness,
- Color = new (_viewToEdit.Padding.ColorScheme?.Normal ?? ColorScheme.Normal),
- SuperViewRendersLineCanvas = true
- };
- _paddingEditor.ThicknessChanged += Editor_ThicknessChanged;
- _paddingEditor.AttributeChanged += Editor_AttributeChanged;
- Add (_paddingEditor);
-
- _diagCheckBox = new () { Text = "_Diagnostics", Y = Pos.Bottom (_paddingEditor) };
- _diagCheckBox.Checked = Diagnostics != ViewDiagnosticFlags.Off;
-
- _diagCheckBox.Toggled += (s, e) =>
- {
- if (e.NewValue == true)
- {
- Diagnostics =
- ViewDiagnosticFlags.Padding | ViewDiagnosticFlags.Ruler;
- }
- else
- {
- Diagnostics = ViewDiagnosticFlags.Off;
- }
- };
-
- Add (_diagCheckBox);
-
- _viewToEdit.LayoutComplete += (s, e) =>
- {
- if (ckbTitle.Checked == true)
- {
- _viewToEdit.Title = _origTitle;
- }
- else
- {
- _viewToEdit.Title = string.Empty;
- }
- };
- }
- }
-
- private void Editor_AttributeChanged (object sender, Attribute attr)
- {
- switch (sender.ToString ())
- {
- case var s when s == _marginEditor.ToString ():
- _viewToEdit.Margin.ColorScheme = new (_viewToEdit.Margin.ColorScheme) { Normal = attr };
-
- break;
- case var s when s == _borderEditor.ToString ():
- _viewToEdit.Border.ColorScheme = new (_viewToEdit.Border.ColorScheme) { Normal = attr };
-
- break;
- case var s when s == _paddingEditor.ToString ():
- _viewToEdit.Padding.ColorScheme =
- new (_viewToEdit.Padding.ColorScheme) { Normal = attr };
-
- break;
- }
- }
-
- private void Editor_ThicknessChanged (object sender, ThicknessEventArgs e)
- {
- try
- {
- switch (sender.ToString ())
- {
- case var s when s == _marginEditor.ToString ():
- _viewToEdit.Margin.Thickness = e.Thickness;
-
- break;
- case var s when s == _borderEditor.ToString ():
- _viewToEdit.Border.Thickness = e.Thickness;
-
- break;
- case var s when s == _paddingEditor.ToString ():
- _viewToEdit.Padding.Thickness = e.Thickness;
-
- break;
- }
- }
- catch
- {
- switch (sender.ToString ())
- {
- case var s when s == _marginEditor.ToString ():
- _viewToEdit.Margin.Thickness = e.PreviousThickness;
-
- break;
- case var s when s == _borderEditor.ToString ():
- _viewToEdit.Border.Thickness = e.PreviousThickness;
-
- break;
- case var s when s == _paddingEditor.ToString ():
- _viewToEdit.Padding.Thickness = e.PreviousThickness;
-
- break;
- }
- }
- }
- }
}
diff --git a/UICatalog/Scenarios/AdornmentsEditor.cs b/UICatalog/Scenarios/AdornmentsEditor.cs
new file mode 100644
index 000000000..a1e40542b
--- /dev/null
+++ b/UICatalog/Scenarios/AdornmentsEditor.cs
@@ -0,0 +1,184 @@
+using System;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+///
+/// Provides an editor UI for the Margin, Border, and Padding of a View.
+///
+public class AdornmentsEditor : View
+{
+ private View _viewToEdit;
+
+ private Label _lblView; // Text describing the vi
+
+ private MarginEditor _marginEditor;
+ private BorderEditor _borderEditor;
+ private PaddingEditor _paddingEditor;
+
+ // TODO: Move Diagnostics to a separate Editor class (DiagnosticsEditor?).
+ private CheckBox _diagPaddingCheckBox;
+ private CheckBox _diagRulerCheckBox;
+ private readonly ViewDiagnosticFlags _savedDiagnosticFlags = Diagnostics;
+
+ public AdornmentsEditor ()
+ {
+ //ColorScheme = Colors.ColorSchemes ["Dialog"];
+ Title = $"AdornmentsEditor";
+
+ Width = Dim.Auto (DimAutoStyle.Content);
+ Height = Dim.Auto (DimAutoStyle.Content);
+
+ //SuperViewRendersLineCanvas = true;
+
+ Application.MouseEvent += Application_MouseEvent;
+ Initialized += AdornmentsEditor_Initialized;
+ }
+
+ ///
+ /// Gets or sets whether the AdornmentsEditor should automatically select the View to edit when the mouse is clicked
+ /// anywhere outside the editor.
+ ///
+ public bool AutoSelectViewToEdit { get; set; }
+
+ private void AdornmentsEditor_Initialized (object sender, EventArgs e)
+ {
+ BorderStyle = LineStyle.Dotted;
+
+ ExpanderButton expandButton = new ExpanderButton ()
+ {
+ Orientation = Orientation.Horizontal
+ };
+ Border.Add (expandButton);
+
+ _lblView = new ()
+ {
+ X = 0,
+ Y = 0,
+ Height = 2,
+ };
+ _lblView.TextFormatter.WordWrap = true;
+ _lblView.TextFormatter.MultiLine = true;
+ _lblView.HotKeySpecifier = (Rune)'\uffff';
+ Add (_lblView);
+
+ _marginEditor = new ()
+ {
+ X = 0,
+ Y = Pos.Bottom (_lblView),
+ SuperViewRendersLineCanvas = true
+ };
+ Add (_marginEditor);
+
+ _lblView.Width = Dim.Width (_marginEditor);
+
+ _borderEditor = new ()
+ {
+ X = Pos.Left (_marginEditor),
+ Y = Pos.Bottom (_marginEditor),
+ SuperViewRendersLineCanvas = true
+ };
+ Add (_borderEditor);
+
+ _paddingEditor = new ()
+ {
+ X = Pos.Left (_borderEditor),
+ Y = Pos.Bottom (_borderEditor),
+ SuperViewRendersLineCanvas = true
+ };
+ Add (_paddingEditor);
+
+ _diagPaddingCheckBox = new () { Text = "_Diagnostic Padding" };
+ _diagPaddingCheckBox.Checked = Diagnostics.FastHasFlags (ViewDiagnosticFlags.Padding);
+
+ _diagPaddingCheckBox.Toggled += (s, e) =>
+ {
+ if (e.NewValue == true)
+ {
+ Diagnostics |= ViewDiagnosticFlags.Padding;
+ }
+ else
+ {
+ Diagnostics &= ~ViewDiagnosticFlags.Padding;
+ }
+ };
+
+ Add (_diagPaddingCheckBox);
+ _diagPaddingCheckBox.Y = Pos.Bottom (_paddingEditor);
+
+ _diagRulerCheckBox = new () { Text = "_Diagnostic Ruler" };
+ _diagRulerCheckBox.Checked = Diagnostics.FastHasFlags (ViewDiagnosticFlags.Ruler);
+
+ _diagRulerCheckBox.Toggled += (s, e) =>
+ {
+ if (e.NewValue == true)
+ {
+ Diagnostics |= ViewDiagnosticFlags.Ruler;
+ }
+ else
+ {
+ Diagnostics &= ~ViewDiagnosticFlags.Ruler;
+ }
+ };
+
+ Add (_diagRulerCheckBox);
+ _diagRulerCheckBox.Y = Pos.Bottom (_diagPaddingCheckBox);
+
+ // BUGBUG: This should not be needed. There's some bug in the layout system that doesn't update the layout.
+ SuperView.LayoutSubviews();
+
+ }
+
+ private void Application_MouseEvent (object sender, MouseEvent e)
+ {
+ if (!AutoSelectViewToEdit || FrameToScreen ().Contains (e.Position))
+ {
+ return;
+ }
+
+ // TODO: Add a setting (property) so only subviews of a specified view are considered.
+ var view = e.View;
+ if (view is { } && e.Flags == MouseFlags.Button1Clicked)
+ {
+ if (view is Adornment adornment)
+ {
+ ViewToEdit = adornment.Parent;
+ }
+ else
+ {
+ ViewToEdit = view;
+ }
+ }
+ }
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ View.Diagnostics = _savedDiagnosticFlags;
+ base.Dispose (disposing);
+ }
+
+ public View ViewToEdit
+ {
+ get => _viewToEdit;
+ set
+ {
+ if (_viewToEdit == value)
+ {
+ return;
+ }
+
+ _viewToEdit = value;
+
+
+ _marginEditor.AdornmentToEdit = _viewToEdit.Margin ?? null;
+ _borderEditor.AdornmentToEdit = _viewToEdit.Border ?? null;
+ _paddingEditor.AdornmentToEdit = _viewToEdit.Padding ?? null;
+
+ _lblView.Text = _viewToEdit.ToString ();
+
+ return;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs
index faea44377..9c74cc529 100644
--- a/UICatalog/Scenarios/AllViewsTester.cs
+++ b/UICatalog/Scenarios/AllViewsTester.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Terminal.Gui;
@@ -19,6 +21,7 @@ public class AllViewsTester : Scenario
private ListView _classListView;
private View _curView;
private FrameView _hostPane;
+ private AdornmentsEditor _adornmentsEditor;
private RadioGroup _hRadioGroup;
private TextField _hText;
private int _hVal;
@@ -42,64 +45,35 @@ public class AllViewsTester : Scenario
private string _demoText = "This, that, and the other thing.";
private TextView _demoTextView;
- public override void Init ()
+ public override void Main ()
{
// Don't create a sub-win (Scenario.Win); just use Application.Top
Application.Init ();
- ConfigurationManager.Themes.Theme = Theme;
ConfigurationManager.Apply ();
- Top = new ();
- Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
- var statusBar = new StatusBar (
- new StatusItem []
- {
- new (
- Application.QuitKey,
- $"{Application.QuitKey} to Quit",
- () => Quit ()
- ),
- new (
- KeyCode.F2,
- "~F2~ Toggle Frame Ruler",
- () =>
- {
- View.Diagnostics ^=
- ViewDiagnosticFlags.Ruler;
- Top.SetNeedsDisplay ();
- }
- ),
- new (
- KeyCode.F3,
- "~F3~ Toggle Frame Padding",
- () =>
- {
- View.Diagnostics ^=
- ViewDiagnosticFlags.Padding;
- Top.SetNeedsDisplay ();
- }
- )
- }
- );
- Top.Add (statusBar);
+ var app = new Window
+ {
+ Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}",
+ ColorScheme = Colors.ColorSchemes ["TopLevel"]
+ };
_viewClasses = GetAllViewClassesCollection ()
.OrderBy (t => t.Name)
.Select (t => new KeyValuePair (t.Name, t))
.ToDictionary (t => t.Key, t => t.Value);
- _leftPane = new FrameView
+ _leftPane = new ()
{
X = 0,
Y = 0,
Width = Dim.Auto (DimAutoStyle.Content),
- Height = Dim.Fill (1), // for status bar
+ Height = Dim.Fill (),
CanFocus = false,
ColorScheme = Colors.ColorSchemes ["TopLevel"],
Title = "Classes"
};
- _classListView = new ListView
+ _classListView = new ()
{
X = 0,
Y = 0,
@@ -108,7 +82,7 @@ public class AllViewsTester : Scenario
AllowsMarking = false,
ColorScheme = Colors.ColorSchemes ["TopLevel"],
SelectedItem = 0,
- Source = new ListWrapper (_viewClasses.Keys.ToList ())
+ Source = new ListWrapper (new (_viewClasses.Keys.ToList ()))
};
_classListView.OpenSelectedItem += (s, a) => { _settingsPane.SetFocus (); };
@@ -128,9 +102,26 @@ public class AllViewsTester : Scenario
};
_leftPane.Add (_classListView);
- _settingsPane = new FrameView
+ _adornmentsEditor = new ()
{
X = Pos.Right (_leftPane),
+ Y = 0,
+ Width = Dim.Auto (),
+ Height = Dim.Fill (),
+ ColorScheme = Colors.ColorSchemes ["TopLevel"],
+ BorderStyle = LineStyle.Single
+ };
+
+ var expandButton = new ExpanderButton
+ {
+ CanFocus = false,
+ Orientation = Orientation.Horizontal
+ };
+ _adornmentsEditor.Border.Add (expandButton);
+
+ _settingsPane = new ()
+ {
+ X = Pos.Right (_adornmentsEditor),
Y = 0, // for menu
Width = Dim.Fill (),
Height = Dim.Auto (),
@@ -141,11 +132,11 @@ public class AllViewsTester : Scenario
string [] radioItems = { "_Percent(x)", "_AnchorEnd", "_Center", "A_bsolute(x)" };
- _locationFrame = new FrameView
+ _locationFrame = new ()
{
X = 0,
Y = 0,
- Height = Dim.Auto (),
+ Height = Dim.Auto (),
Width = Dim.Auto (),
Title = "Location (Pos)"
};
@@ -153,9 +144,9 @@ public class AllViewsTester : Scenario
var label = new Label { X = 0, Y = 0, Text = "X:" };
_locationFrame.Add (label);
- _xRadioGroup = new RadioGroup { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
+ _xRadioGroup = new () { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
_xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
- _xText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_xVal}" };
+ _xText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_xVal}" };
_xText.Accept += (s, args) =>
{
@@ -172,9 +163,9 @@ public class AllViewsTester : Scenario
_locationFrame.Add (_xRadioGroup);
radioItems = new [] { "P_ercent(y)", "A_nchorEnd", "C_enter", "Absolute(_y)" };
- label = new Label { X = Pos.Right (_xRadioGroup) + 1, Y = 0, Text = "Y:" };
+ label = new () { X = Pos.Right (_xRadioGroup) + 1, Y = 0, Text = "Y:" };
_locationFrame.Add (label);
- _yText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_yVal}" };
+ _yText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_yVal}" };
_yText.Accept += (s, args) =>
{
@@ -187,11 +178,11 @@ public class AllViewsTester : Scenario
{ }
};
_locationFrame.Add (_yText);
- _yRadioGroup = new RadioGroup { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
+ _yRadioGroup = new () { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
_yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
_locationFrame.Add (_yRadioGroup);
- _sizeFrame = new FrameView
+ _sizeFrame = new ()
{
X = Pos.Right (_locationFrame),
Y = Pos.Y (_locationFrame),
@@ -201,42 +192,42 @@ public class AllViewsTester : Scenario
};
radioItems = new [] { "Auto", "_Percent(width)", "_Fill(width)", "A_bsolute(width)" };
- label = new Label { X = 0, Y = 0, Text = "Width:" };
+ label = new () { X = 0, Y = 0, Text = "Width:" };
_sizeFrame.Add (label);
- _wRadioGroup = new RadioGroup { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
+ _wRadioGroup = new () { X = 0, Y = Pos.Bottom (label), RadioLabels = radioItems };
_wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
- _wText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_wVal}" };
+ _wText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_wVal}" };
_wText.Accept += (s, args) =>
- {
- try
- {
- switch (_wRadioGroup.SelectedItem)
- {
- case 1:
- _wVal = Math.Min (int.Parse (_wText.Text), 100);
+ {
+ try
+ {
+ switch (_wRadioGroup.SelectedItem)
+ {
+ case 1:
+ _wVal = Math.Min (int.Parse (_wText.Text), 100);
- break;
- case 0:
- case 2:
- case 3:
- _wVal = int.Parse (_wText.Text);
+ break;
+ case 0:
+ case 2:
+ case 3:
+ _wVal = int.Parse (_wText.Text);
- break;
- }
+ break;
+ }
- DimPosChanged (_curView);
- }
- catch
- { }
- };
+ DimPosChanged (_curView);
+ }
+ catch
+ { }
+ };
_sizeFrame.Add (_wText);
_sizeFrame.Add (_wRadioGroup);
radioItems = new [] { "_Auto", "P_ercent(height)", "F_ill(height)", "Ab_solute(height)" };
- label = new Label { X = Pos.Right (_wRadioGroup) + 1, Y = 0, Text = "Height:" };
+ label = new () { X = Pos.Right (_wRadioGroup) + 1, Y = 0, Text = "Height:" };
_sizeFrame.Add (label);
- _hText = new TextField { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_hVal}" };
+ _hText = new () { X = Pos.Right (label) + 1, Y = 0, Width = 4, Text = $"{_hVal}" };
_hText.Accept += (s, args) =>
{
@@ -263,30 +254,33 @@ public class AllViewsTester : Scenario
};
_sizeFrame.Add (_hText);
- _hRadioGroup = new RadioGroup { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
+ _hRadioGroup = new () { X = Pos.X (label), Y = Pos.Bottom (label), RadioLabels = radioItems };
_hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
_sizeFrame.Add (_hRadioGroup);
_settingsPane.Add (_sizeFrame);
- label = new Label { X = 0, Y = Pos.Bottom (_sizeFrame), Text = "_Orientation:" };
- _orientation = new RadioGroup
+ label = new () { X = 0, Y = Pos.Bottom (_sizeFrame), Text = "_Orientation:" };
+
+ _orientation = new ()
{
X = Pos.Right (label) + 1,
Y = Pos.Top (label),
RadioLabels = new [] { "Horizontal", "Vertical" },
Orientation = Orientation.Horizontal
};
+
_orientation.SelectedItemChanged += (s, selected) =>
{
- if (_curView?.GetType ().GetProperty ("Orientation") is {} prop)
+ if (_curView?.GetType ().GetProperty ("Orientation") is { } prop)
{
prop.GetSetMethod ()?.Invoke (_curView, new object [] { _orientation.SelectedItem });
}
};
_settingsPane.Add (label, _orientation);
- label = new Label { X = 0, Y = Pos.Bottom (_orientation), Text = "_Text:" };
+ label = new () { X = 0, Y = Pos.Bottom (_orientation), Text = "_Text:" };
+
_demoTextView = new ()
{
X = Pos.Right (label) + 1,
@@ -295,26 +289,35 @@ public class AllViewsTester : Scenario
Height = Dim.Auto (minimumContentDim: 2),
Text = _demoText
};
+
_demoTextView.ContentsChanged += (s, e) =>
- {
- _demoText = _demoTextView.Text;
- _curView.Text = _demoText;
- };
+ {
+ _demoText = _demoTextView.Text;
+
+ if (_curView is { })
+ {
+ _curView.Text = _demoText;
+ }
+ };
_settingsPane.Add (label, _demoTextView);
- _hostPane = new FrameView
+ _hostPane = new ()
{
- X = Pos.Right (_leftPane),
+ X = Pos.Right (_adornmentsEditor),
Y = Pos.Bottom (_settingsPane),
Width = Dim.Fill (),
- Height = Dim.Fill (1), // + 1 for status bar
+ Height = Dim.Fill (), // + 1 for status bar
ColorScheme = Colors.ColorSchemes ["Dialog"]
};
- Top.Add (_leftPane, _settingsPane, _hostPane);
+ app.Add (_leftPane, _adornmentsEditor, _settingsPane, _hostPane);
- _curView = CreateClass (_viewClasses.First ().Value);
+ _classListView.SelectedItem = 0;
+
+ Application.Run (app);
+ app.Dispose ();
+ Application.Shutdown ();
}
// TODO: Add Command.HotKey handler (pop a message box?)
@@ -384,14 +387,14 @@ public class AllViewsTester : Scenario
// If the view supports a Source property, set it so we have something to look at
if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource))
{
- var source = new ListWrapper (new List { "Test Text #1", "Test Text #2", "Test Text #3" });
- view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
+ var source = new ListWrapper (["Test Text #1", "Test Text #2", "Test Text #3"]);
+ view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, [source]);
}
// If the view supports a Title property, set it so we have something to look at
- if (view?.GetType ().GetProperty ("Orientation") is {} prop)
+ if (view?.GetType ().GetProperty ("Orientation") is { } prop)
{
- _orientation.SelectedItem = (int)prop.GetGetMethod()!.Invoke (view, null)!;
+ _orientation.SelectedItem = (int)prop.GetGetMethod ()!.Invoke (view, null)!;
_orientation.Enabled = true;
}
else
@@ -415,47 +418,43 @@ public class AllViewsTester : Scenario
return;
}
- LayoutStyle layout = view.LayoutStyle;
-
try
{
- //view.LayoutStyle = LayoutStyle.Absolute;
-
view.X = _xRadioGroup.SelectedItem switch
- {
- 0 => Pos.Percent (_xVal),
- 1 => Pos.AnchorEnd (),
- 2 => Pos.Center (),
- 3 => Pos.Absolute (_xVal),
- _ => view.X
- };
+ {
+ 0 => Pos.Percent (_xVal),
+ 1 => Pos.AnchorEnd (),
+ 2 => Pos.Center (),
+ 3 => Pos.Absolute (_xVal),
+ _ => view.X
+ };
view.Y = _yRadioGroup.SelectedItem switch
- {
- 0 => Pos.Percent (_yVal),
- 1 => Pos.AnchorEnd (),
- 2 => Pos.Center (),
- 3 => Pos.Absolute (_yVal),
- _ => view.Y
- };
+ {
+ 0 => Pos.Percent (_yVal),
+ 1 => Pos.AnchorEnd (),
+ 2 => Pos.Center (),
+ 3 => Pos.Absolute (_yVal),
+ _ => view.Y
+ };
view.Width = _wRadioGroup.SelectedItem switch
- {
- 0 => Dim.Auto (),
- 1 => Dim.Percent (_wVal),
- 2 => Dim.Fill (_wVal),
- 3 => Dim.Absolute (_wVal),
- _ => view.Width
- };
+ {
+ 0 => Dim.Auto (),
+ 1 => Dim.Percent (_wVal),
+ 2 => Dim.Fill (_wVal),
+ 3 => Dim.Absolute (_wVal),
+ _ => view.Width
+ };
view.Height = _hRadioGroup.SelectedItem switch
- {
- 0 => Dim.Auto (),
- 1 => Dim.Percent (_hVal),
- 2 => Dim.Fill (_hVal),
- 3 => Dim.Absolute (_hVal),
- _ => view.Height
- };
+ {
+ 0 => Dim.Auto (),
+ 1 => Dim.Percent (_hVal),
+ 2 => Dim.Fill (_hVal),
+ 3 => Dim.Absolute (_hVal),
+ _ => view.Height
+ };
}
catch (Exception e)
{
@@ -464,7 +463,7 @@ public class AllViewsTester : Scenario
if (view.Width is DimAuto)
{
- _wText.Text = $"Auto";
+ _wText.Text = "Auto";
_wText.Enabled = false;
}
else
@@ -475,7 +474,7 @@ public class AllViewsTester : Scenario
if (view.Height is DimAuto)
{
- _hText.Text = $"Auto";
+ _hText.Text = "Auto";
_hText.Enabled = false;
}
else
@@ -484,8 +483,6 @@ public class AllViewsTester : Scenario
_hText.Enabled = true;
}
-
-
UpdateTitle (view);
}
@@ -517,39 +514,51 @@ public class AllViewsTester : Scenario
private void UpdateSettings (View view)
{
+ _adornmentsEditor.ViewToEdit = view;
+
var x = view.X.ToString ();
var y = view.Y.ToString ();
- _xRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => x.Contains (s)).First ());
- _yRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => y.Contains (s)).First ());
+
+ try
+ {
+ _xRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.First (s => x.Contains (s)));
+ _yRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.First (s => y.Contains (s)));
+ }
+ catch (InvalidOperationException e)
+ {
+ // This is a hack to work around the fact that the Pos enum doesn't have an "Align" value yet
+ Debug.WriteLine($"{e}");
+ }
+
_xText.Text = $"{view.Frame.X}";
_yText.Text = $"{view.Frame.Y}";
var w = view.Width.ToString ();
var h = view.Height.ToString ();
- _wRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => w.Contains (s)).First ());
- _hRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => h.Contains (s)).First ());
+ _wRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.First (s => w.Contains (s)));
+ _hRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.First (s => h.Contains (s)));
+
if (view.Width is DimAuto)
{
- _wText.Text = $"Auto";
+ _wText.Text = "Auto";
_wText.Enabled = false;
}
else
{
- _wText.Text = $"100";
+ _wText.Text = "100";
_wText.Enabled = true;
}
if (view.Height is DimAuto)
{
- _hText.Text = $"Auto";
+ _hText.Text = "Auto";
_hText.Enabled = false;
}
else
{
- _hText.Text = $"100";
+ _hText.Text = "100";
_hText.Enabled = true;
}
-
}
private void UpdateTitle (View view) { _hostPane.Title = $"{view.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}"; }
diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
index e2d4b8c78..e6e35debb 100644
--- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs
+++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
@@ -247,7 +248,7 @@ public class BackgroundWorkerCollection : Scenario
private readonly ListView _listView;
private readonly Button _start;
- public StagingUIController (Staging staging, List list) : this ()
+ public StagingUIController (Staging staging, ObservableCollection list) : this ()
{
Staging = staging;
_label.Text = "Work list:";
@@ -335,7 +336,7 @@ public class BackgroundWorkerCollection : Scenario
private class WorkerApp : Toplevel
{
private readonly ListView _listLog;
- private readonly List _log = [];
+ private readonly ObservableCollection _log = [];
private List _stagingsUi;
private Dictionary _stagingWorkers;
@@ -357,7 +358,7 @@ public class BackgroundWorkerCollection : Scenario
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill (),
- Source = new ListWrapper (_log)
+ Source = new ListWrapper (_log)
};
Add (_listLog);
@@ -464,7 +465,7 @@ public class BackgroundWorkerCollection : Scenario
);
Application.Refresh ();
- var stagingUI = new StagingUIController (staging, e.Result as List)
+ var stagingUI = new StagingUIController (staging, e.Result as ObservableCollection)
{
Modal = false,
Title =
diff --git a/UICatalog/Scenarios/BasicColors.cs b/UICatalog/Scenarios/BasicColors.cs
index 1780d7cbb..b1b75c54f 100644
--- a/UICatalog/Scenarios/BasicColors.cs
+++ b/UICatalog/Scenarios/BasicColors.cs
@@ -32,7 +32,7 @@ public class BasicColors : Scenario
Y = 0,
Width = 1,
Height = 13,
- VerticalTextAlignment = VerticalTextAlignment.Bottom,
+ VerticalTextAlignment = Alignment.End,
ColorScheme = new ColorScheme { Normal = attr },
Text = bg.ToString (),
TextDirection = TextDirection.TopBottom_LeftRight
@@ -45,7 +45,7 @@ public class BasicColors : Scenario
Y = y,
Width = 13,
Height = 1,
- TextAlignment = TextAlignment.Right,
+ TextAlignment = Alignment.End,
ColorScheme = new ColorScheme { Normal = attr },
Text = bg.ToString ()
};
diff --git a/UICatalog/Scenarios/BorderEditor.cs b/UICatalog/Scenarios/BorderEditor.cs
new file mode 100644
index 000000000..9af7e1dd3
--- /dev/null
+++ b/UICatalog/Scenarios/BorderEditor.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+public class BorderEditor : AdornmentEditor
+{
+ private CheckBox _ckbTitle;
+ private RadioGroup _rbBorderStyle;
+
+ public BorderEditor ()
+ {
+ Title = "_Border";
+ Initialized += BorderEditor_Initialized;
+ AdornmentChanged += BorderEditor_AdornmentChanged;
+
+ }
+
+ private void BorderEditor_AdornmentChanged (object sender, EventArgs e)
+ {
+ _ckbTitle.Checked = ((Border)AdornmentToEdit).ShowTitle;
+ _rbBorderStyle.SelectedItem = (int)((Border)AdornmentToEdit).LineStyle;
+ }
+
+ private void BorderEditor_Initialized (object sender, EventArgs e)
+ {
+
+ List borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast ().ToList ();
+
+ _rbBorderStyle = new RadioGroup
+ {
+ X = 0,
+ Y = Pos.Bottom (Subviews [^1]),
+ Width = Dim.Width (Subviews [^2]) + Dim.Width (Subviews [^1]) - 1,
+ SelectedItem = (int)(((Border)AdornmentToEdit)?.LineStyle ?? LineStyle.None),
+ BorderStyle = LineStyle.Single,
+ Title = "Border St_yle",
+ SuperViewRendersLineCanvas = true,
+ Enabled = AdornmentToEdit is { },
+ RadioLabels = borderStyleEnum.Select (e => e.ToString ()).ToArray ()
+ };
+ Add (_rbBorderStyle);
+
+ _rbBorderStyle.SelectedItemChanged += OnRbBorderStyleOnSelectedItemChanged;
+
+ _ckbTitle = new CheckBox
+ {
+ X = 0,
+ Y = Pos.Bottom (_rbBorderStyle),
+
+ Checked = true,
+ SuperViewRendersLineCanvas = true,
+ Text = "Show Title",
+ Enabled = AdornmentToEdit is { }
+ };
+
+
+ _ckbTitle.Toggled += OnCkbTitleOnToggled;
+ Add (_ckbTitle);
+
+ return;
+
+ void OnRbBorderStyleOnSelectedItemChanged (object s, SelectedItemChangedArgs e)
+ {
+ LineStyle prevBorderStyle = AdornmentToEdit.BorderStyle;
+ ((Border)AdornmentToEdit).LineStyle = (LineStyle)e.SelectedItem;
+
+ if (((Border)AdornmentToEdit).LineStyle == LineStyle.None)
+ {
+ ((Border)AdornmentToEdit).Thickness = new (0);
+ }
+ else if (prevBorderStyle == LineStyle.None && ((Border)AdornmentToEdit).LineStyle != LineStyle.None)
+ {
+ ((Border)AdornmentToEdit).Thickness = new (1);
+ }
+
+ ((Border)AdornmentToEdit).SetNeedsDisplay ();
+ LayoutSubviews ();
+ }
+
+ void OnCkbTitleOnToggled (object sender, StateEventArgs args) { ((Border)AdornmentToEdit).ShowTitle = args.NewValue!.Value; }
+ }
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs
index e05ff5bfb..370a5eb66 100644
--- a/UICatalog/Scenarios/Buttons.cs
+++ b/UICatalog/Scenarios/Buttons.cs
@@ -218,7 +218,7 @@ public class Buttons : Scenario
X = 4,
Y = Pos.Bottom (label) + 1,
SelectedItem = 2,
- RadioLabels = new [] { "Left", "Right", "Centered", "Justified" }
+ RadioLabels = new [] { "Start", "End", "Center", "Fill" }
};
main.Add (radioGroup);
@@ -287,39 +287,39 @@ public class Buttons : Scenario
switch (args.SelectedItem)
{
case 0:
- moveBtn.TextAlignment = TextAlignment.Left;
- sizeBtn.TextAlignment = TextAlignment.Left;
- moveBtnA.TextAlignment = TextAlignment.Left;
- sizeBtnA.TextAlignment = TextAlignment.Left;
- moveHotKeyBtn.TextAlignment = TextAlignment.Left;
- moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left;
+ moveBtn.TextAlignment = Alignment.Start;
+ sizeBtn.TextAlignment = Alignment.Start;
+ moveBtnA.TextAlignment = Alignment.Start;
+ sizeBtnA.TextAlignment = Alignment.Start;
+ moveHotKeyBtn.TextAlignment = Alignment.Start;
+ moveUnicodeHotKeyBtn.TextAlignment = Alignment.Start;
break;
case 1:
- moveBtn.TextAlignment = TextAlignment.Right;
- sizeBtn.TextAlignment = TextAlignment.Right;
- moveBtnA.TextAlignment = TextAlignment.Right;
- sizeBtnA.TextAlignment = TextAlignment.Right;
- moveHotKeyBtn.TextAlignment = TextAlignment.Right;
- moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right;
+ moveBtn.TextAlignment = Alignment.End;
+ sizeBtn.TextAlignment = Alignment.End;
+ moveBtnA.TextAlignment = Alignment.End;
+ sizeBtnA.TextAlignment = Alignment.End;
+ moveHotKeyBtn.TextAlignment = Alignment.End;
+ moveUnicodeHotKeyBtn.TextAlignment = Alignment.End;
break;
case 2:
- moveBtn.TextAlignment = TextAlignment.Centered;
- sizeBtn.TextAlignment = TextAlignment.Centered;
- moveBtnA.TextAlignment = TextAlignment.Centered;
- sizeBtnA.TextAlignment = TextAlignment.Centered;
- moveHotKeyBtn.TextAlignment = TextAlignment.Centered;
- moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered;
+ moveBtn.TextAlignment = Alignment.Center;
+ sizeBtn.TextAlignment = Alignment.Center;
+ moveBtnA.TextAlignment = Alignment.Center;
+ sizeBtnA.TextAlignment = Alignment.Center;
+ moveHotKeyBtn.TextAlignment = Alignment.Center;
+ moveUnicodeHotKeyBtn.TextAlignment = Alignment.Center;
break;
case 3:
- moveBtn.TextAlignment = TextAlignment.Justified;
- sizeBtn.TextAlignment = TextAlignment.Justified;
- moveBtnA.TextAlignment = TextAlignment.Justified;
- sizeBtnA.TextAlignment = TextAlignment.Justified;
- moveHotKeyBtn.TextAlignment = TextAlignment.Justified;
- moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified;
+ moveBtn.TextAlignment = Alignment.Fill;
+ sizeBtn.TextAlignment = Alignment.Fill;
+ moveBtnA.TextAlignment = Alignment.Fill;
+ sizeBtnA.TextAlignment = Alignment.Fill;
+ moveHotKeyBtn.TextAlignment = Alignment.Fill;
+ moveUnicodeHotKeyBtn.TextAlignment = Alignment.Fill;
break;
}
@@ -418,9 +418,8 @@ public class Buttons : Scenario
throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction.");
}
- // TODO: Use Dim.Auto for the Width and Height
- Height = 1;
- Width = Dim.Func (() => _number is null ? Digits + 2 : Math.Max (Digits + 2, _number.Text.Length + 2)); // button + 3 for number + button
+ Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button
+ Height = Dim.Auto (DimAutoStyle.Content);
_down = new ()
{
@@ -440,7 +439,7 @@ public class Buttons : Scenario
Y = Pos.Top (_down),
Width = Dim.Func (() => _number is null ? Digits : Math.Max (Digits, _number.Text.Length)),
Height = 1,
- TextAlignment = TextAlignment.Centered,
+ TextAlignment = Alignment.Center,
CanFocus = true
};
diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs
index 0a32490de..02c11da5c 100644
--- a/UICatalog/Scenarios/CharacterMap.cs
+++ b/UICatalog/Scenarios/CharacterMap.cs
@@ -958,7 +958,7 @@ internal class CharMap : View
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill (1),
- TextAlignment = TextAlignment.Centered
+ TextAlignment = Alignment.Center
};
var spinner = new SpinnerView { X = Pos.Center (), Y = Pos.Center (), Style = new Aesthetic () };
spinner.AutoSpin = true;
@@ -1081,7 +1081,7 @@ internal class CharMap : View
};
dlg.Add (label);
- var json = new TextView
+ var json = new TextView ()
{
X = 0,
Y = Pos.Bottom (label),
@@ -1090,6 +1090,7 @@ internal class CharMap : View
ReadOnly = true,
Text = decResponse
};
+
dlg.Add (json);
Application.Run (dlg);
diff --git a/UICatalog/Scenarios/Clipping.cs b/UICatalog/Scenarios/Clipping.cs
index 43d1d80ca..94cfc4751 100644
--- a/UICatalog/Scenarios/Clipping.cs
+++ b/UICatalog/Scenarios/Clipping.cs
@@ -23,12 +23,13 @@ public class Clipping : Scenario
//Win.Height = Dim.Fill () - 2;
var label = new Label
{
- X = 0, Y = 0, Text = "ScrollView (new Rectangle (3, 3, 50, 20)) with a 200, 100 ContentSize..."
+ X = 0, Y = 0, Text = "ScrollView (new Rectangle (3, 3, 50, 20)) with a 200, 100 GetContentSize ()..."
};
Top.Add (label);
var scrollView = new ScrollView { X = 3, Y = 3, Width = 50, Height = 20 };
scrollView.ColorScheme = Colors.ColorSchemes ["Menu"];
+ // BUGBUG: set_ContentSize is supposed to be `protected`.
scrollView.SetContentSize (new (200, 100));
//ContentOffset = Point.Empty,
diff --git a/UICatalog/Scenarios/CollectionNavigatorTester.cs b/UICatalog/Scenarios/CollectionNavigatorTester.cs
index ef31ac6a7..abdb2c3e1 100644
--- a/UICatalog/Scenarios/CollectionNavigatorTester.cs
+++ b/UICatalog/Scenarios/CollectionNavigatorTester.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using Terminal.Gui;
@@ -16,7 +17,7 @@ namespace UICatalog.Scenarios;
[ScenarioCategory ("Mouse and Keyboard")]
public class CollectionNavigatorTester : Scenario
{
- private readonly List _items = new []
+ private ObservableCollection _items = new ObservableCollection (new ObservableCollection ()
{
"a",
"b",
@@ -71,7 +72,7 @@ public class CollectionNavigatorTester : Scenario
"q",
"quit",
"quitter"
- }.ToList ();
+ }.ToList ());
private ListView _listView;
private TreeView _treeView;
@@ -129,7 +130,7 @@ public class CollectionNavigatorTester : Scenario
Top.Add (menu);
- _items.Sort (StringComparer.OrdinalIgnoreCase);
+ _items = new (_items.OrderBy (i => i, StringComparer.OrdinalIgnoreCase));
CreateListView ();
var vsep = new LineView (Orientation.Vertical) { X = Pos.Right (_listView), Y = 1, Height = Dim.Fill () };
@@ -142,7 +143,7 @@ public class CollectionNavigatorTester : Scenario
var label = new Label
{
Text = "ListView",
- TextAlignment = TextAlignment.Centered,
+ TextAlignment = Alignment.Center,
X = 0,
Y = 1, // for menu
Width = Dim.Percent (50),
@@ -171,7 +172,7 @@ public class CollectionNavigatorTester : Scenario
var label = new Label
{
Text = "TreeView",
- TextAlignment = TextAlignment.Centered,
+ TextAlignment = Alignment.Center,
X = Pos.Right (_listView) + 2,
Y = 1, // for menu
Width = Dim.Percent (50),
diff --git a/UICatalog/Scenarios/ColorPicker.cs b/UICatalog/Scenarios/ColorPicker.cs
index fe2b08fad..5bc84b70d 100644
--- a/UICatalog/Scenarios/ColorPicker.cs
+++ b/UICatalog/Scenarios/ColorPicker.cs
@@ -69,8 +69,8 @@ public class ColorPickers : Scenario
{
Title = "Color Sample",
Text = "Lorem Ipsum",
- TextAlignment = TextAlignment.Centered,
- VerticalTextAlignment = VerticalTextAlignment.Middle,
+ TextAlignment = Alignment.Center,
+ VerticalTextAlignment = Alignment.Center,
BorderStyle = LineStyle.Heavy,
X = Pos.Center (),
Y = Pos.Center (),
diff --git a/UICatalog/Scenarios/ComboBoxIteration.cs b/UICatalog/Scenarios/ComboBoxIteration.cs
index a62fba18c..1d0cf9742 100644
--- a/UICatalog/Scenarios/ComboBoxIteration.cs
+++ b/UICatalog/Scenarios/ComboBoxIteration.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using Terminal.Gui;
namespace UICatalog.Scenarios;
@@ -10,14 +11,14 @@ public class ComboBoxIteration : Scenario
{
public override void Setup ()
{
- List items = new () { "one", "two", "three" };
+ ObservableCollection items = ["one", "two", "three"];
var lbListView = new Label { Width = 10, Height = 1 };
Win.Add (lbListView);
var listview = new ListView
{
- Y = Pos.Bottom (lbListView) + 1, Width = 10, Height = Dim.Fill (2), Source = new ListWrapper (items)
+ Y = Pos.Bottom (lbListView) + 1, Width = 10, Height = Dim.Fill (2), Source = new ListWrapper (items)
};
Win.Add (listview);
@@ -59,7 +60,7 @@ public class ComboBoxIteration : Scenario
btnTwo.Accept += (s, e) =>
{
- items = new List { "one", "two" };
+ items = ["one", "two"];
comboBox.SetSource (items);
listview.SetSource (items);
listview.SelectedItem = 0;
@@ -70,7 +71,7 @@ public class ComboBoxIteration : Scenario
btnThree.Accept += (s, e) =>
{
- items = new List { "one", "two", "three" };
+ items =["one", "two", "three"];
comboBox.SetSource (items);
listview.SetSource (items);
listview.SelectedItem = 0;
diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs
index 5e6eb5cd2..a28e866f8 100644
--- a/UICatalog/Scenarios/ComputedLayout.cs
+++ b/UICatalog/Scenarios/ComputedLayout.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Terminal.Gui;
+using static Terminal.Gui.Dialog;
namespace UICatalog.Scenarios;
@@ -85,12 +86,12 @@ public class ComputedLayout : Scenario
var i = 1;
var txt = "Resize the terminal to see computed layout in action.";
List