diff --git a/Terminal.Gui/View/Orientation/IOrientation.cs b/Terminal.Gui/View/Orientation/IOrientation.cs
new file mode 100644
index 000000000..34470878e
--- /dev/null
+++ b/Terminal.Gui/View/Orientation/IOrientation.cs
@@ -0,0 +1,42 @@
+
+namespace Terminal.Gui;
+using System;
+
+///
+/// Implement this interface to provide orientation support.
+///
+///
+/// See for a helper class that implements this interface.
+///
+public interface IOrientation
+{
+ ///
+ /// Gets or sets the orientation of the View.
+ ///
+ Orientation Orientation { get; set; }
+
+ ///
+ /// Raised when is changing. Can be cancelled.
+ ///
+ public event EventHandler> OrientationChanging;
+
+ ///
+ /// Called when is changing.
+ ///
+ /// The current orientation.
+ /// The new orientation.
+ /// to cancel the change.
+ public bool OnOrientationChanging (Orientation currentOrientation, Orientation newOrientation) { return false; }
+
+ ///
+ /// Raised when has changed.
+ ///
+ public event EventHandler> OrientationChanged;
+
+ ///
+ /// Called when has been changed.
+ ///
+ ///
+ ///
+ public void OnOrientationChanged (Orientation newOrientation) { return; }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Views/GraphView/Orientation.cs b/Terminal.Gui/View/Orientation/Orientation.cs
similarity index 100%
rename from Terminal.Gui/Views/GraphView/Orientation.cs
rename to Terminal.Gui/View/Orientation/Orientation.cs
diff --git a/Terminal.Gui/View/Orientation/OrientationHelper.cs b/Terminal.Gui/View/Orientation/OrientationHelper.cs
new file mode 100644
index 000000000..2227494dc
--- /dev/null
+++ b/Terminal.Gui/View/Orientation/OrientationHelper.cs
@@ -0,0 +1,138 @@
+namespace Terminal.Gui;
+
+///
+/// Helper class for implementing .
+///
+///
+///
+/// Implements the standard pattern for changing/changed events.
+///
+///
+///
+///
+/// private class OrientedView : View, IOrientation
+/// {
+/// private readonly OrientationHelper _orientationHelper;
+///
+/// public OrientedView ()
+/// {
+/// _orientationHelper = new (this);
+/// Orientation = Orientation.Vertical;
+/// _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+/// _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+/// }
+///
+/// public Orientation Orientation
+/// {
+/// get => _orientationHelper.Orientation;
+/// set => _orientationHelper.Orientation = value;
+/// }
+///
+/// public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
+/// public event EventHandler<EventArgs<Orientation>> OrientationChanged;
+///
+/// public bool OnOrientationChanging (Orientation currentOrientation, Orientation newOrientation)
+/// {
+/// // Custom logic before orientation changes
+/// return false; // Return true to cancel the change
+/// }
+///
+/// public void OnOrientationChanged (Orientation newOrientation)
+/// {
+/// // Custom logic after orientation has changed
+/// }
+/// }
+///
+///
+public class OrientationHelper
+{
+ private Orientation _orientation;
+ private readonly IOrientation _owner;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Specifies the object that owns this helper instance and implements .
+ public OrientationHelper (IOrientation owner) { _owner = owner; }
+
+ ///
+ /// Gets or sets the orientation of the View.
+ ///
+ public Orientation Orientation
+ {
+ get => _orientation;
+ set
+ {
+ if (_orientation == value)
+ {
+ return;
+ }
+
+ // Best practice is to invoke the virtual method first.
+ // This allows derived classes to handle the event and potentially cancel it.
+ if (_owner?.OnOrientationChanging (value, _orientation) ?? false)
+ {
+ return;
+ }
+
+ // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+ CancelEventArgs args = new (in _orientation, ref value);
+ OrientationChanging?.Invoke (_owner, args);
+
+ if (args.Cancel)
+ {
+ return;
+ }
+
+ // If the event is not canceled, update the value.
+ Orientation old = _orientation;
+
+ if (_orientation != value)
+ {
+ _orientation = value;
+
+ if (_owner is { })
+ {
+ _owner.Orientation = value;
+ }
+ }
+
+ // Best practice is to invoke the virtual method first.
+ _owner?.OnOrientationChanged (_orientation);
+
+ // Even though Changed is not cancelable, it is still a good practice to raise the event after.
+ OrientationChanged?.Invoke (_owner, new (in _orientation));
+ }
+ }
+
+ ///
+ /// Raised when the orientation is changing. This is cancelable.
+ ///
+ ///
+ ///
+ /// Views that implement should raise
+ /// after the orientation has changed
+ /// (_orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);).
+ ///
+ ///
+ /// This event will be raised after the method is called (assuming
+ /// it was not canceled).
+ ///
+ ///
+ public event EventHandler> OrientationChanging;
+
+ ///
+ /// Raised when the orientation has changed.
+ ///
+ ///
+ ///
+ /// Views that implement should raise
+ /// after the orientation has changed
+ /// (_orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);).
+ ///
+ ///
+ /// This event will be raised after the method is called.
+ ///
+ ///
+ public event EventHandler> OrientationChanged;
+}
diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs
index 720baab73..0871a13f4 100644
--- a/Terminal.Gui/Views/Bar.cs
+++ b/Terminal.Gui/Views/Bar.cs
@@ -11,8 +11,10 @@ namespace Terminal.Gui;
/// align them in a specific order.
///
///
-public class Bar : View
+public class Bar : View, IOrientation, IDesignable
{
+ private readonly OrientationHelper _orientationHelper;
+
///
public Bar () : this ([]) { }
@@ -24,6 +26,10 @@ public class Bar : View
Width = Dim.Auto ();
Height = Dim.Auto ();
+ _orientationHelper = new (this);
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+
Initialized += Bar_Initialized;
if (shortcuts is null)
@@ -46,7 +52,7 @@ public class Bar : View
Border.LineStyle = value;
}
- private Orientation _orientation = Orientation.Horizontal;
+ #region IOrientation members
///
/// Gets or sets the for this . The default is
@@ -58,16 +64,27 @@ public class Bar : View
/// Vertical orientation arranges the command, help, and key parts of each s from left to right.
///
///
+
public Orientation Orientation
{
- get => _orientation;
- set
- {
- _orientation = value;
- SetNeedsLayout ();
- }
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
}
+ ///
+ public event EventHandler> OrientationChanging;
+
+ ///
+ public event EventHandler> OrientationChanged;
+
+ /// Called when has changed.
+ ///
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+ SetNeedsLayout ();
+ }
+ #endregion
+
private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd;
///
@@ -224,4 +241,28 @@ public class Bar : View
break;
}
}
+
+ ///
+ public bool EnableForDesign ()
+ {
+ var shortcut = new Shortcut
+ {
+ Text = "Quit",
+ Title = "Q_uit",
+ Key = Key.Z.WithCtrl,
+ };
+
+ Add (shortcut);
+
+ shortcut = new Shortcut
+ {
+ Text = "Help Text",
+ Title = "Help",
+ Key = Key.F1,
+ };
+
+ Add (shortcut);
+
+ return true;
+ }
}
diff --git a/Terminal.Gui/Views/Line.cs b/Terminal.Gui/Views/Line.cs
index 6a25fa55b..730d5a1aa 100644
--- a/Terminal.Gui/Views/Line.cs
+++ b/Terminal.Gui/Views/Line.cs
@@ -1,43 +1,60 @@
namespace Terminal.Gui;
/// Draws a single line using the specified by .
-public class Line : View
+public class Line : View, IOrientation
{
+ private readonly OrientationHelper _orientationHelper;
+
/// Constructs a Line object.
public Line ()
{
BorderStyle = LineStyle.Single;
Border.Thickness = new Thickness (0);
SuperViewRendersLineCanvas = true;
+
+ _orientationHelper = new (this);
+ _orientationHelper.Orientation = Orientation.Horizontal;
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
}
- private Orientation _orientation;
+ #region IOrientation members
///
/// The direction of the line. If you change this you will need to manually update the Width/Height of the
/// control to cover a relevant area based on the new direction.
///
public Orientation Orientation
{
- get => _orientation;
- set
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
+ }
+
+ ///
+ public event EventHandler> OrientationChanging;
+
+ ///
+ public event EventHandler> OrientationChanged;
+
+ /// Called when has changed.
+ ///
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+
+ switch (newOrientation)
{
- _orientation = value;
+ case Orientation.Horizontal:
+ Height = 1;
- switch (Orientation)
- {
- case Orientation.Horizontal:
- Height = 1;
+ break;
+ case Orientation.Vertical:
+ Width = 1;
- break;
- case Orientation.Vertical:
- Width = 1;
+ break;
- break;
-
- }
}
}
+ #endregion
///
public override void SetBorderStyle (LineStyle value)
diff --git a/Terminal.Gui/Views/OrientationEventArgs.cs b/Terminal.Gui/Views/OrientationEventArgs.cs
deleted file mode 100644
index 8a633ca83..000000000
--- a/Terminal.Gui/Views/OrientationEventArgs.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-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/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs
index f0dc5174b..5ce71c7a4 100644
--- a/Terminal.Gui/Views/RadioGroup.cs
+++ b/Terminal.Gui/Views/RadioGroup.cs
@@ -1,14 +1,14 @@
namespace Terminal.Gui;
/// Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.
-public class RadioGroup : View, IDesignable
+public class RadioGroup : View, IDesignable, IOrientation
{
private int _cursor;
private List<(int pos, int length)> _horizontal;
private int _horizontalSpace = 2;
- private Orientation _orientation = Orientation.Vertical;
private List _radioLabels = [];
private int _selected;
+ private readonly OrientationHelper _orientationHelper;
///
/// Initializes a new instance of the class.
@@ -44,6 +44,7 @@ public class RadioGroup : View, IDesignable
{
return false;
}
+
MoveDownRight ();
return true;
@@ -58,6 +59,7 @@ public class RadioGroup : View, IDesignable
{
return false;
}
+
MoveHome ();
return true;
@@ -72,6 +74,7 @@ public class RadioGroup : View, IDesignable
{
return false;
}
+
MoveEnd ();
return true;
@@ -93,6 +96,7 @@ public class RadioGroup : View, IDesignable
ctx =>
{
SetFocus ();
+
if (ctx.KeyBinding?.Context is { } && (int)ctx.KeyBinding?.Context! < _radioLabels.Count)
{
SelectedItem = (int)ctx.KeyBinding?.Context!;
@@ -103,6 +107,11 @@ public class RadioGroup : View, IDesignable
return true;
});
+ _orientationHelper = new (this);
+ _orientationHelper.Orientation = Orientation.Vertical;
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+
SetupKeyBindings ();
LayoutStarted += RadioGroup_LayoutStarted;
@@ -142,15 +151,15 @@ public class RadioGroup : View, IDesignable
int viewportX = e.MouseEvent.Position.X;
int viewportY = e.MouseEvent.Position.Y;
- int pos = _orientation == Orientation.Horizontal ? viewportX : viewportY;
+ int pos = Orientation == Orientation.Horizontal ? viewportX : viewportY;
- int rCount = _orientation == Orientation.Horizontal
+ int rCount = Orientation == Orientation.Horizontal
? _horizontal.Last ().pos + _horizontal.Last ().length
: _radioLabels.Count;
if (pos < rCount)
{
- int c = _orientation == Orientation.Horizontal
+ int c = Orientation == Orientation.Horizontal
? _horizontal.FindIndex (x => x.pos <= viewportX && x.pos + x.length - 2 >= viewportX)
: viewportY;
@@ -173,7 +182,7 @@ public class RadioGroup : View, IDesignable
get => _horizontalSpace;
set
{
- if (_horizontalSpace != value && _orientation == Orientation.Horizontal)
+ if (_horizontalSpace != value && Orientation == Orientation.Horizontal)
{
_horizontalSpace = value;
UpdateTextFormatterText ();
@@ -182,16 +191,6 @@ public class RadioGroup : View, IDesignable
}
}
- ///
- /// Gets or sets the for this . The default is
- /// .
- ///
- public Orientation Orientation
- {
- get => _orientation;
- set => OnOrientationChanged (value);
- }
-
///
/// The radio labels to display. A key binding will be added for each radio enabling the user to select
/// and/or focus the radio label using the keyboard. See for details on how HotKeys work.
@@ -323,44 +322,49 @@ public class RadioGroup : View, IDesignable
}
}
- /// Called when the view orientation has changed. Invokes the event.
- ///
- /// True of the event was cancelled.
- public virtual bool OnOrientationChanged (Orientation newOrientation)
+ ///
+ /// Gets or sets the for this . The default is
+ /// .
+ ///
+ public Orientation Orientation
{
- var args = new OrientationEventArgs (newOrientation);
- OrientationChanged?.Invoke (this, args);
-
- if (!args.Cancel)
- {
- _orientation = newOrientation;
- SetupKeyBindings ();
- SetContentSize ();
- }
-
- return args.Cancel;
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
}
+ #region IOrientation
+
+ ///
+ public event EventHandler> OrientationChanging;
+
+ ///
+ public event EventHandler> OrientationChanged;
+
+ /// Called when has changed.
+ ///
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+ SetupKeyBindings ();
+ SetContentSize ();
+ }
+
+ #endregion IOrientation
+
// TODO: This should be cancelable
/// Called whenever the current selected item changes. Invokes the event.
///
///
public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
- {
+ {
if (_selected == selectedItem)
{
return;
}
+
_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 ()
{
@@ -374,7 +378,10 @@ public class RadioGroup : View, IDesignable
break;
case Orientation.Horizontal:
- x = _horizontal [_cursor].pos;
+ if (_horizontal.Count > 0)
+ {
+ x = _horizontal [_cursor].pos;
+ }
break;
@@ -429,7 +436,7 @@ public class RadioGroup : View, IDesignable
private void SetContentSize ()
{
- switch (_orientation)
+ switch (Orientation)
{
case Orientation.Vertical:
var width = 0;
@@ -462,10 +469,11 @@ public class RadioGroup : View, IDesignable
}
}
- ///
+ ///
public bool EnableForDesign ()
{
RadioLabels = new [] { "Option _1", "Option _2", "Option _3" };
+
return true;
}
}
diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs
index e49d5a4e1..c7eb4df42 100644
--- a/Terminal.Gui/Views/Shortcut.cs
+++ b/Terminal.Gui/Views/Shortcut.cs
@@ -1,10 +1,8 @@
-using System.ComponentModel;
-using System.Threading.Channels;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
///
-/// Displays a command, help text, and a key binding. When the key specified by is pressed, the command will be invoked. Useful for
+/// Displays a command, help text, and a key binding. When the key specified by is pressed, the
+/// command will be invoked. Useful for
/// displaying a command in such as a
/// menu, toolbar, or status bar.
///
@@ -12,12 +10,13 @@ namespace Terminal.Gui;
///
/// The following user actions will invoke the , causing the
/// event to be fired:
-/// - Clicking on the .
-/// - Pressing the key specified by .
-/// - Pressing the HotKey specified by .
+/// - Clicking on the .
+/// - Pressing the key specified by .
+/// - Pressing the HotKey specified by .
///
///
-/// If is , will invoked
+/// If is , will invoked
+///
/// command regardless of what View has focus, enabling an application-wide keyboard shortcut.
///
///
@@ -37,8 +36,10 @@ namespace Terminal.Gui;
/// If the is , the text is not displayed.
///
///
-public class Shortcut : View
+public class Shortcut : View, IOrientation, IDesignable
{
+ private readonly OrientationHelper _orientationHelper;
+
///
/// Creates a new instance of .
///
@@ -60,6 +61,10 @@ public class Shortcut : View
Width = GetWidthDimAuto ();
Height = Dim.Auto (DimAutoStyle.Content, 1);
+ _orientationHelper = new (this);
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+
AddCommand (Command.HotKey, ctx => OnAccept (ctx));
AddCommand (Command.Accept, ctx => OnAccept (ctx));
AddCommand (Command.Select, ctx => OnSelect (ctx));
@@ -132,31 +137,48 @@ public class Shortcut : View
}
}
-
///
/// Creates a new instance of .
///
public Shortcut () : this (Key.Empty, string.Empty, null) { }
- private Orientation _orientation = Orientation.Horizontal;
+ #region IOrientation members
///
- /// Gets or sets the for this . The default is
- /// , which is ideal for status bar, menu bar, and tool bar items If set to
- /// ,
- /// the Shortcut will be configured for vertical layout, which is ideal for menu items.
+ /// Gets or sets the for this . The default is
+ /// .
///
+ ///
+ ///
+ /// Horizontal orientation arranges the command, help, and key parts of each s from right to
+ /// left
+ /// Vertical orientation arranges the command, help, and key parts of each s from left to
+ /// right.
+ ///
+ ///
+
public Orientation Orientation
{
- get => _orientation;
- set
- {
- _orientation = value;
-
- // TODO: Determine what, if anything, is opinionated about the orientation.
- }
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
}
+ ///
+ public event EventHandler> OrientationChanging;
+
+ ///
+ public event EventHandler> OrientationChanged;
+
+ /// Called when has changed.
+ ///
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+ // TODO: Determine what, if anything, is opinionated about the orientation.
+ SetNeedsLayout ();
+ }
+
+ #endregion
+
private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast;
///
@@ -344,7 +366,6 @@ public class Shortcut : View
private void Subview_MouseClick (object sender, MouseEventEventArgs e)
{
// TODO: Remove. This does nothing.
- return;
}
#region Command
@@ -434,359 +455,368 @@ public class Shortcut : View
SetKeyViewDefaultLayout ();
ShowHide ();
UpdateKeyBinding ();
-
- return;
}
}
private void SetCommandViewDefaultLayout ()
-{
- CommandView.Margin.Thickness = GetMarginThickness ();
- CommandView.X = Pos.Align (Alignment.End, AlignmentModes);
- CommandView.Y = 0; //Pos.Center ();
-}
-
-private void Shortcut_TitleChanged (object sender, EventArgs e)
-{
- // If the Title changes, update the CommandView text.
- // This is a helper to make it easier to set the CommandView text.
- // CommandView is public and replaceable, but this is a convenience.
- _commandView.Text = Title;
-}
-
-#endregion Command
-
-#region Help
-
-///
-/// The subview that displays the help text for the command. Internal for unit testing.
-///
-internal View HelpView { get; } = new ();
-
-private void SetHelpViewDefaultLayout ()
-{
- HelpView.Margin.Thickness = GetMarginThickness ();
- HelpView.X = Pos.Align (Alignment.End, AlignmentModes);
- HelpView.Y = 0; //Pos.Center ();
- HelpView.Width = Dim.Auto (DimAutoStyle.Text);
- HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
-
- HelpView.Visible = true;
- HelpView.VerticalTextAlignment = Alignment.Center;
-}
-
-///
-/// Gets or sets the help text displayed in the middle of the Shortcut. Identical in function to
-/// .
-///
-public override string Text
-{
- get => HelpView?.Text;
- set
{
- if (HelpView is {})
+ CommandView.Margin.Thickness = GetMarginThickness ();
+ CommandView.X = Pos.Align (Alignment.End, AlignmentModes);
+ CommandView.Y = 0; //Pos.Center ();
+ }
+
+ private void Shortcut_TitleChanged (object sender, EventArgs e)
+ {
+ // If the Title changes, update the CommandView text.
+ // This is a helper to make it easier to set the CommandView text.
+ // CommandView is public and replaceable, but this is a convenience.
+ _commandView.Text = Title;
+ }
+
+ #endregion Command
+
+ #region Help
+
+ ///
+ /// The subview that displays the help text for the command. Internal for unit testing.
+ ///
+ internal View HelpView { get; } = new ();
+
+ private void SetHelpViewDefaultLayout ()
+ {
+ HelpView.Margin.Thickness = GetMarginThickness ();
+ HelpView.X = Pos.Align (Alignment.End, AlignmentModes);
+ HelpView.Y = 0; //Pos.Center ();
+ HelpView.Width = Dim.Auto (DimAutoStyle.Text);
+ HelpView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
+
+ HelpView.Visible = true;
+ HelpView.VerticalTextAlignment = Alignment.Center;
+ }
+
+ ///
+ /// Gets or sets the help text displayed in the middle of the Shortcut. Identical in function to
+ /// .
+ ///
+ public override string Text
+ {
+ get => HelpView?.Text;
+ set
{
- HelpView.Text = value;
- ShowHide ();
- }
- }
-}
-
-///
-/// Gets or sets the help text displayed in the middle of the Shortcut.
-///
-public string HelpText
-{
- get => HelpView?.Text;
- set
- {
- if (HelpView is {})
- {
- HelpView.Text = value;
- ShowHide ();
- }
- }
-}
-
-#endregion Help
-
-#region Key
-
-private Key _key = Key.Empty;
-
-///
-/// Gets or sets the that will be bound to the command.
-///
-public Key Key
-{
- get => _key;
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException ();
- }
-
- _key = value;
-
- UpdateKeyBinding ();
-
- KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}";
- ShowHide ();
- }
-}
-
-private KeyBindingScope _keyBindingScope = KeyBindingScope.HotKey;
-
-///
-/// Gets or sets the scope for the key binding for how is bound to .
-///
-public KeyBindingScope KeyBindingScope
-{
- get => _keyBindingScope;
- set
- {
- _keyBindingScope = value;
-
- UpdateKeyBinding ();
- }
-}
-
-///
-/// Gets the subview that displays the key. Internal for unit testing.
-///
-
-internal View KeyView { get; } = new ();
-
-private int _minimumKeyTextSize;
-
-///
-/// Gets or sets the minimum size of the key text. Useful for aligning the key text with other s.
-///
-public int MinimumKeyTextSize
-{
- get => _minimumKeyTextSize;
- set
- {
- if (value == _minimumKeyTextSize)
- {
- //return;
- }
-
- _minimumKeyTextSize = value;
- SetKeyViewDefaultLayout ();
- CommandView.SetNeedsLayout ();
- HelpView.SetNeedsLayout ();
- KeyView.SetNeedsLayout ();
- SetSubViewNeedsDisplay ();
- }
-}
-
-private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; }
-
-private void SetKeyViewDefaultLayout ()
-{
- KeyView.Margin.Thickness = GetMarginThickness ();
- KeyView.X = Pos.Align (Alignment.End, AlignmentModes);
- KeyView.Y = 0; //Pos.Center ();
- KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize));
- KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
-
- KeyView.Visible = true;
-
- // Right align the text in the keyview
- KeyView.TextAlignment = Alignment.End;
- KeyView.VerticalTextAlignment = Alignment.Center;
- KeyView.KeyBindings.Clear ();
-}
-
-private void UpdateKeyBinding ()
-{
- if (Key != null)
- {
- // Disable the command view key bindings
- CommandView.KeyBindings.Remove (Key);
- CommandView.KeyBindings.Remove (CommandView.HotKey);
- KeyBindings.Remove (Key);
- KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept);
- //KeyBindings.Add (Key, KeyBindingScope.HotKey, Command.Accept);
- }
-}
-
-#endregion Key
-
-#region Accept Handling
-
-///
-/// Called when the command is received. This
-/// occurs
-/// - if the user clicks anywhere on the shortcut with the mouse
-/// - if the user presses Key
-/// - if the user presses the HotKey specified by CommandView
-/// - if HasFocus and the user presses Space or Enter (or any other key bound to Command.Accept).
-///
-protected bool? OnAccept (CommandContext ctx)
-{
- var cancel = false;
-
- switch (ctx.KeyBinding?.Scope)
- {
- case KeyBindingScope.Application:
- cancel = base.OnAccept () == true;
-
- break;
-
- case KeyBindingScope.Focused:
- base.OnAccept ();
-
- // cancel if we're focused
- cancel = true;
-
- break;
-
- case KeyBindingScope.HotKey:
- cancel = base.OnAccept () == true;
-
- if (CanFocus)
+ if (HelpView is {})
{
- SetFocus ();
- cancel = true;
+ HelpView.Text = value;
+ ShowHide ();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the help text displayed in the middle of the Shortcut.
+ ///
+ public string HelpText
+ {
+ get => HelpView?.Text;
+ set
+ {
+ if (HelpView is {})
+ {
+ HelpView.Text = value;
+ ShowHide ();
+ }
+ }
+ }
+
+ #endregion Help
+
+ #region Key
+
+ private Key _key = Key.Empty;
+
+ ///
+ /// Gets or sets the that will be bound to the command.
+ ///
+ public Key Key
+ {
+ get => _key;
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException ();
}
- break;
+ _key = value;
- default:
- // Mouse
- cancel = base.OnAccept () == true;
+ UpdateKeyBinding ();
- break;
+ KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}";
+ ShowHide ();
+ }
}
- CommandView.InvokeCommand (Command.Accept, ctx.Key, ctx.KeyBinding);
+ private KeyBindingScope _keyBindingScope = KeyBindingScope.HotKey;
- if (Action is { })
+ ///
+ /// Gets or sets the scope for the key binding for how is bound to .
+ ///
+ public KeyBindingScope KeyBindingScope
{
- Action.Invoke ();
- // Assume if there's a subscriber to Action, it's handled.
- cancel = true;
+ get => _keyBindingScope;
+ set
+ {
+ _keyBindingScope = value;
+
+ UpdateKeyBinding ();
+ }
}
- return cancel;
-}
+ ///
+ /// Gets the subview that displays the key. Internal for unit testing.
+ ///
-///
-/// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the
-/// mouse.
-///
-///
-/// Note, the event is fired first, and if cancelled, the event will not be invoked.
-///
-[CanBeNull]
-public Action Action { get; set; }
+ internal View KeyView { get; } = new ();
-#endregion Accept Handling
+ private int _minimumKeyTextSize;
-private bool? OnSelect (CommandContext ctx)
-{
- if (CommandView.GetSupportedCommands ().Contains (Command.Select))
+ ///
+ /// Gets or sets the minimum size of the key text. Useful for aligning the key text with other s.
+ ///
+ public int MinimumKeyTextSize
{
- return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding);
+ get => _minimumKeyTextSize;
+ set
+ {
+ if (value == _minimumKeyTextSize)
+ {
+ //return;
+ }
+
+ _minimumKeyTextSize = value;
+ SetKeyViewDefaultLayout ();
+ CommandView.SetNeedsLayout ();
+ HelpView.SetNeedsLayout ();
+ KeyView.SetNeedsLayout ();
+ SetSubViewNeedsDisplay ();
+ }
}
- return false;
-}
+ private int GetMinimumKeyViewSize () { return MinimumKeyTextSize; }
-
-#region Focus
-
-///
-public override ColorScheme ColorScheme
-{
- get => base.ColorScheme;
- set
+ private void SetKeyViewDefaultLayout ()
+ {
+ KeyView.Margin.Thickness = GetMarginThickness ();
+ KeyView.X = Pos.Align (Alignment.End, AlignmentModes);
+ KeyView.Y = 0; //Pos.Center ();
+ KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize));
+ KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
+
+ KeyView.Visible = true;
+
+ // Right align the text in the keyview
+ KeyView.TextAlignment = Alignment.End;
+ KeyView.VerticalTextAlignment = Alignment.Center;
+ KeyView.KeyBindings.Clear ();
+ }
+
+ private void UpdateKeyBinding ()
+ {
+ if (Key != null)
+ {
+ // Disable the command view key bindings
+ CommandView.KeyBindings.Remove (Key);
+ CommandView.KeyBindings.Remove (CommandView.HotKey);
+ KeyBindings.Remove (Key);
+ KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept);
+
+ //KeyBindings.Add (Key, KeyBindingScope.HotKey, Command.Accept);
+ }
+ }
+
+ #endregion Key
+
+ #region Accept Handling
+
+ ///
+ /// Called when the command is received. This
+ /// occurs
+ /// - if the user clicks anywhere on the shortcut with the mouse
+ /// - if the user presses Key
+ /// - if the user presses the HotKey specified by CommandView
+ /// - if HasFocus and the user presses Space or Enter (or any other key bound to Command.Accept).
+ ///
+ protected bool? OnAccept (CommandContext ctx)
+ {
+ var cancel = false;
+
+ switch (ctx.KeyBinding?.Scope)
+ {
+ case KeyBindingScope.Application:
+ cancel = base.OnAccept () == true;
+
+ break;
+
+ case KeyBindingScope.Focused:
+ base.OnAccept ();
+
+ // cancel if we're focused
+ cancel = true;
+
+ break;
+
+ case KeyBindingScope.HotKey:
+ cancel = base.OnAccept () == true;
+
+ if (CanFocus)
+ {
+ SetFocus ();
+ cancel = true;
+ }
+
+ break;
+
+ default:
+ // Mouse
+ cancel = base.OnAccept () == true;
+
+ break;
+ }
+
+ CommandView.InvokeCommand (Command.Accept, ctx.Key, ctx.KeyBinding);
+
+ if (Action is { })
+ {
+ Action.Invoke ();
+
+ // Assume if there's a subscriber to Action, it's handled.
+ cancel = true;
+ }
+
+ return cancel;
+ }
+
+ ///
+ /// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the
+ /// mouse.
+ ///
+ ///
+ /// Note, the event is fired first, and if cancelled, the event will not be invoked.
+ ///
+ [CanBeNull]
+ public Action Action { get; set; }
+
+ #endregion Accept Handling
+
+ private bool? OnSelect (CommandContext ctx)
+ {
+ if (CommandView.GetSupportedCommands ().Contains (Command.Select))
+ {
+ return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding);
+ }
+
+ return false;
+ }
+
+ #region Focus
+
+ ///
+ public override ColorScheme ColorScheme
+ {
+ get => base.ColorScheme;
+ set
+ {
+ base.ColorScheme = value;
+ SetColors ();
+ }
+ }
+
+ ///
+ ///
+ internal void SetColors ()
+ {
+ // Border should match superview.
+ Border.ColorScheme = SuperView?.ColorScheme;
+
+ if (HasFocus)
+ {
+ // When we have focus, we invert the colors
+ base.ColorScheme = new (base.ColorScheme)
+ {
+ Normal = base.ColorScheme.Focus,
+ HotNormal = base.ColorScheme.HotFocus,
+ HotFocus = base.ColorScheme.HotNormal,
+ Focus = base.ColorScheme.Normal
+ };
+ }
+ else
+ {
+ base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme;
+ }
+
+ // Set KeyView's colors to show "hot"
+ if (IsInitialized && base.ColorScheme is { })
+ {
+ var cs = new ColorScheme (base.ColorScheme)
+ {
+ Normal = base.ColorScheme.HotNormal,
+ HotNormal = base.ColorScheme.Normal
+ };
+ KeyView.ColorScheme = cs;
+ }
+ }
+
+ private View _lastFocusedView;
+
+ ///
+ public override bool OnEnter (View view)
{
- base.ColorScheme = value;
SetColors ();
+ _lastFocusedView = view;
+
+ return base.OnEnter (view);
}
-}
-///
-///
-internal void SetColors ()
-{
- // Border should match superview.
- Border.ColorScheme = SuperView?.ColorScheme;
-
- if (HasFocus)
+ ///
+ public override bool OnLeave (View view)
{
- // When we have focus, we invert the colors
- base.ColorScheme = new (base.ColorScheme)
+ SetColors ();
+ _lastFocusedView = this;
+
+ return base.OnLeave (view);
+ }
+
+ #endregion Focus
+
+ ///
+ public bool EnableForDesign ()
+ {
+ Title = "_Shortcut";
+ HelpText = "Shortcut help";
+ Key = Key.F1;
+ return true;
+ }
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ if (disposing)
{
- Normal = base.ColorScheme.Focus,
- HotNormal = base.ColorScheme.HotFocus,
- HotFocus = base.ColorScheme.HotNormal,
- Focus = base.ColorScheme.Normal
- };
- }
- else
- {
- base.ColorScheme = SuperView?.ColorScheme ?? base.ColorScheme;
- }
+ if (CommandView?.IsAdded == false)
+ {
+ CommandView.Dispose ();
+ }
- // Set KeyView's colors to show "hot"
- if (IsInitialized && base.ColorScheme is { })
- {
- var cs = new ColorScheme (base.ColorScheme)
- {
- Normal = base.ColorScheme.HotNormal,
- HotNormal = base.ColorScheme.Normal
- };
- KeyView.ColorScheme = cs;
- }
-}
+ if (HelpView?.IsAdded == false)
+ {
+ HelpView.Dispose ();
+ }
-View _lastFocusedView;
-///
-public override bool OnEnter (View view)
-{
- SetColors ();
- _lastFocusedView = view;
-
- return base.OnEnter (view);
-}
-
-///
-public override bool OnLeave (View view)
-{
- SetColors ();
- _lastFocusedView = this;
-
- return base.OnLeave (view);
-}
-
-#endregion Focus
-
-///
-protected override void Dispose (bool disposing)
-{
- if (disposing)
- {
- if (CommandView?.IsAdded == false)
- {
- CommandView.Dispose ();
+ if (KeyView?.IsAdded == false)
+ {
+ KeyView.Dispose ();
+ }
}
- if (HelpView?.IsAdded == false)
- {
- HelpView.Dispose ();
- }
-
- if (KeyView?.IsAdded == false)
- {
- KeyView.Dispose ();
- }
+ base.Dispose (disposing);
}
-
- base.Dispose (disposing);
-}
}
diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs
index 61ac401d6..69a9add71 100644
--- a/Terminal.Gui/Views/Slider.cs
+++ b/Terminal.Gui/Views/Slider.cs
@@ -21,7 +21,7 @@ public class Slider : Slider
///
-public class Slider : View
+public class Slider : View, IOrientation
{
private readonly SliderConfiguration _config = new ();
@@ -31,6 +31,8 @@ public class Slider : View
// Options
private List> _options;
+ private OrientationHelper _orientationHelper;
+
#region Initialize
private void SetInitialProperties (
@@ -45,11 +47,13 @@ public class Slider : View
_options = options ?? new List> ();
- _config._sliderOrientation = orientation;
+ _orientationHelper = new (this);
+ _orientationHelper.Orientation = _config._sliderOrientation = orientation;
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
SetDefaultStyle ();
SetCommands ();
-
SetContentSize ();
// BUGBUG: This should not be needed - Need to ensure SetRelativeLayout gets called during EndInit
@@ -222,13 +226,46 @@ public class Slider : View
}
}
- /// Slider Orientation.
+
+ ///
+ /// Gets or sets the . The default is .
+ ///
public Orientation Orientation
{
- get => _config._sliderOrientation;
- set => OnOrientationChanged (value);
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
}
+ #region IOrientation members
+
+ ///
+ public event EventHandler> OrientationChanging;
+
+ ///
+ public event EventHandler> OrientationChanged;
+
+ ///
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+ _config._sliderOrientation = newOrientation;
+
+ switch (_config._sliderOrientation)
+ {
+ case Orientation.Horizontal:
+ Style.SpaceChar = new () { Rune = Glyphs.HLine }; // '─'
+
+ break;
+ case Orientation.Vertical:
+ Style.SpaceChar = new () { Rune = Glyphs.VLine };
+
+ break;
+ }
+
+ SetKeyBindings ();
+ SetContentSize ();
+ }
+ #endregion
+
/// Legends Orientation.
public Orientation LegendsOrientation
{
@@ -309,43 +346,6 @@ public class Slider : View
#region Events
- ///
- /// Fired when the slider orientation has changed. Can be cancelled by setting
- /// to true.
- ///
- public event EventHandler OrientationChanged;
-
- /// Called when the slider 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)
- {
- _config._sliderOrientation = newOrientation;
-
- switch (_config._sliderOrientation)
- {
- case Orientation.Horizontal:
- Style.SpaceChar = new () { Rune = Glyphs.HLine }; // '─'
-
- break;
- case Orientation.Vertical:
- Style.SpaceChar = new () { Rune = Glyphs.VLine };
-
- break;
- }
-
- SetKeyBindings ();
- SetContentSize ();
- }
-
- return args.Cancel;
- }
-
/// Event raised when the slider option/s changed. The dictionary contains: key = option index, value = T
public event EventHandler> OptionsChanged;
@@ -1738,7 +1738,7 @@ public class Slider : View
internal bool Select ()
{
- SetFocusedOption();
+ SetFocusedOption ();
return true;
}
diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs
index b4df14e6b..4136335cf 100644
--- a/Terminal.Gui/Views/StatusBar.cs
+++ b/Terminal.Gui/Views/StatusBar.cs
@@ -10,7 +10,7 @@ namespace Terminal.Gui;
/// to ask a file to load is executed, the remaining commands will probably be ~F1~ Help. So for each context must be a
/// new instance of a status bar.
///
-public class StatusBar : Bar
+public class StatusBar : Bar, IDesignable
{
///
public StatusBar () : this ([]) { }
@@ -74,4 +74,74 @@ public class StatusBar : Bar
return view;
}
+
+ ///
+ bool IDesignable.EnableForDesign ()
+ {
+ var shortcut = new Shortcut
+ {
+ Text = "Quit",
+ Title = "Q_uit",
+ Key = Key.Z.WithCtrl,
+ };
+
+ Add (shortcut);
+
+ shortcut = new Shortcut
+ {
+ Text = "Help Text",
+ Title = "Help",
+ Key = Key.F1,
+ };
+
+ Add (shortcut);
+
+ shortcut = new Shortcut
+ {
+ Title = "_Show/Hide",
+ Key = Key.F10,
+ CommandView = new CheckBox
+ {
+ CanFocus = false,
+ Text = "_Show/Hide"
+ },
+ };
+
+ Add (shortcut);
+
+ var button1 = new Button
+ {
+ Text = "I'll Hide",
+ // Visible = false
+ };
+ button1.Accept += Button_Clicked;
+ Add (button1);
+
+ shortcut.Accept += (s, e) =>
+ {
+ button1.Visible = !button1.Visible;
+ button1.Enabled = button1.Visible;
+ e.Handled = false;
+ };
+
+ Add (new Label
+ {
+ HotKeySpecifier = new Rune ('_'),
+ Text = "Fo_cusLabel",
+ CanFocus = true
+ });
+
+ var button2 = new Button
+ {
+ Text = "Or me!",
+ };
+ button2.Accept += (s, e) => Application.RequestStop ();
+
+ Add (button2);
+
+ return true;
+
+ void Button_Clicked (object sender, EventArgs e) { MessageBox.Query ("Hi", $"You clicked {sender}"); }
+ }
+
}
diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs
index cc440b820..ed5d74bf6 100644
--- a/UICatalog/Scenarios/AllViewsTester.cs
+++ b/UICatalog/Scenarios/AllViewsTester.cs
@@ -272,9 +272,9 @@ public class AllViewsTester : Scenario
_orientation.SelectedItemChanged += (s, selected) =>
{
- if (_curView?.GetType ().GetProperty ("Orientation") is { } prop)
+ if (_curView is IOrientation orientatedView)
{
- prop.GetSetMethod ()?.Invoke (_curView, new object [] { _orientation.SelectedItem });
+ orientatedView.Orientation = (Orientation)_orientation.SelectedItem;
}
};
_settingsPane.Add (label, _orientation);
@@ -358,11 +358,9 @@ public class AllViewsTester : Scenario
view.Title = "_Test Title";
}
- // TODO: Add IOrientation so this doesn't require reflection
- // 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 is IOrientation orientatedView)
{
- _orientation.SelectedItem = (int)prop.GetGetMethod ()!.Invoke (view, null)!;
+ _orientation.SelectedItem = (int)orientatedView.Orientation;
_orientation.Enabled = true;
}
else
diff --git a/UICatalog/Scenarios/Bars.cs b/UICatalog/Scenarios/Bars.cs
index d494a79c4..929774919 100644
--- a/UICatalog/Scenarios/Bars.cs
+++ b/UICatalog/Scenarios/Bars.cs
@@ -402,7 +402,7 @@ public class Bars : Scenario
bar.Add (shortcut1, shortcut2, line, shortcut3);
}
- private void ConfigStatusBar (Bar bar)
+ public void ConfigStatusBar (Bar bar)
{
var shortcut = new Shortcut
{
diff --git a/UICatalog/Scenarios/ExpanderButton.cs b/UICatalog/Scenarios/ExpanderButton.cs
index 3cbe02d7f..bf1622135 100644
--- a/UICatalog/Scenarios/ExpanderButton.cs
+++ b/UICatalog/Scenarios/ExpanderButton.cs
@@ -74,7 +74,7 @@ public class ExpanderButton : Button
/// True of the event was cancelled.
protected virtual bool OnOrientationChanging (Orientation newOrientation)
{
- var args = new OrientationEventArgs (newOrientation);
+ var args = new CancelEventArgs (in _orientation, ref newOrientation);
OrientationChanging?.Invoke (this, args);
if (!args.Cancel)
@@ -103,10 +103,9 @@ public class ExpanderButton : Button
}
///
- /// Fired when the orientation has changed. Can be cancelled by setting
- /// to true.
+ /// Fired when the orientation has changed. Can be cancelled.
///
- public event EventHandler OrientationChanging;
+ public event EventHandler> OrientationChanging;
///
/// The glyph to display when the view is collapsed.
diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj
index 2a9fbc394..941e25c9b 100644
--- a/UnitTests/UnitTests.csproj
+++ b/UnitTests/UnitTests.csproj
@@ -30,9 +30,10 @@
-
-
-
+
+
+
+
@@ -58,6 +59,9 @@
+
+
+
False
diff --git a/UnitTests/View/Orientation/OrientationHelperTests.cs b/UnitTests/View/Orientation/OrientationHelperTests.cs
new file mode 100644
index 000000000..cc6a1f240
--- /dev/null
+++ b/UnitTests/View/Orientation/OrientationHelperTests.cs
@@ -0,0 +1,107 @@
+using Moq;
+
+namespace Terminal.Gui.ViewTests.OrientationTests;
+
+public class OrientationHelperTests
+{
+ [Fact]
+ public void Orientation_Set_NewValue_InvokesChangingAndChangedEvents ()
+ {
+ // Arrange
+ Mock mockIOrientation = new Mock ();
+ var orientationHelper = new OrientationHelper (mockIOrientation.Object);
+ var changingEventInvoked = false;
+ var changedEventInvoked = false;
+
+ orientationHelper.OrientationChanging += (sender, e) => { changingEventInvoked = true; };
+ orientationHelper.OrientationChanged += (sender, e) => { changedEventInvoked = true; };
+
+ // Act
+ orientationHelper.Orientation = Orientation.Vertical;
+
+ // Assert
+ Assert.True (changingEventInvoked, "OrientationChanging event was not invoked.");
+ Assert.True (changedEventInvoked, "OrientationChanged event was not invoked.");
+ }
+
+ [Fact]
+ public void Orientation_Set_NewValue_InvokesOnChangingAndOnChangedOverrides ()
+ {
+ // Arrange
+ Mock mockIOrientation = new Mock ();
+ var onChangingOverrideCalled = false;
+ var onChangedOverrideCalled = false;
+
+ mockIOrientation.Setup (x => x.OnOrientationChanging (It.IsAny (), It.IsAny ()))
+ .Callback (() => onChangingOverrideCalled = true)
+ .Returns (false); // Ensure it doesn't cancel the change
+
+ mockIOrientation.Setup (x => x.OnOrientationChanged (It.IsAny ()))
+ .Callback (() => onChangedOverrideCalled = true);
+
+ var orientationHelper = new OrientationHelper (mockIOrientation.Object);
+
+ // Act
+ orientationHelper.Orientation = Orientation.Vertical;
+
+ // Assert
+ Assert.True (onChangingOverrideCalled, "OnOrientationChanging override was not called.");
+ Assert.True (onChangedOverrideCalled, "OnOrientationChanged override was not called.");
+ }
+
+ [Fact]
+ public void Orientation_Set_SameValue_DoesNotInvokeChangingOrChangedEvents ()
+ {
+ // Arrange
+ Mock mockIOrientation = new Mock ();
+ var orientationHelper = new OrientationHelper (mockIOrientation.Object);
+ orientationHelper.Orientation = Orientation.Horizontal; // Set initial orientation
+ var changingEventInvoked = false;
+ var changedEventInvoked = false;
+
+ orientationHelper.OrientationChanging += (sender, e) => { changingEventInvoked = true; };
+ orientationHelper.OrientationChanged += (sender, e) => { changedEventInvoked = true; };
+
+ // Act
+ orientationHelper.Orientation = Orientation.Horizontal; // Set to the same value
+
+ // Assert
+ Assert.False (changingEventInvoked, "OrientationChanging event was invoked.");
+ Assert.False (changedEventInvoked, "OrientationChanged event was invoked.");
+ }
+
+ [Fact]
+ public void Orientation_Set_NewValue_OrientationChanging_CancellationPreventsChange ()
+ {
+ // Arrange
+ Mock mockIOrientation = new Mock ();
+ var orientationHelper = new OrientationHelper (mockIOrientation.Object);
+ orientationHelper.OrientationChanging += (sender, e) => { e.Cancel = true; }; // Cancel the change
+
+ // Act
+ orientationHelper.Orientation = Orientation.Vertical;
+
+ // Assert
+ Assert.Equal (Orientation.Horizontal, orientationHelper.Orientation); // Initial orientation is Horizontal
+ }
+
+ [Fact]
+ public void Orientation_Set_NewValue_OnOrientationChanging_CancelsChange ()
+ {
+ // Arrange
+ Mock mockIOrientation = new Mock ();
+
+ mockIOrientation.Setup (x => x.OnOrientationChanging (It.IsAny (), It.IsAny ()))
+ .Returns (true); // Override to return true, cancelling the change
+
+ var orientationHelper = new OrientationHelper (mockIOrientation.Object);
+
+ // Act
+ orientationHelper.Orientation = Orientation.Vertical;
+
+ // Assert
+ Assert.Equal (
+ Orientation.Horizontal,
+ orientationHelper.Orientation); // Initial orientation is Horizontal, and it should remain unchanged due to cancellation
+ }
+}
diff --git a/UnitTests/View/Orientation/OrientationTests.cs b/UnitTests/View/Orientation/OrientationTests.cs
new file mode 100644
index 000000000..d000ca9db
--- /dev/null
+++ b/UnitTests/View/Orientation/OrientationTests.cs
@@ -0,0 +1,136 @@
+namespace Terminal.Gui.ViewTests.OrientationTests;
+
+public class OrientationTests
+{
+ private class CustomView : View, IOrientation
+ {
+ private readonly OrientationHelper _orientationHelper;
+
+ public CustomView ()
+ {
+ _orientationHelper = new (this);
+ Orientation = Orientation.Vertical;
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+ }
+
+ public Orientation Orientation
+ {
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
+ }
+
+ public event EventHandler> OrientationChanging;
+ public event EventHandler> OrientationChanged;
+
+ public bool CancelOnOrientationChanging { get; set; }
+
+ public bool OnOrientationChangingCalled { get; private set; }
+ public bool OnOrientationChangedCalled { get; private set; }
+
+ public bool OnOrientationChanging (Orientation currentOrientation, Orientation newOrientation)
+ {
+ OnOrientationChangingCalled = true;
+ // Custom logic before orientation changes
+ return CancelOnOrientationChanging; // Return true to cancel the change
+ }
+
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+ OnOrientationChangedCalled = true;
+ // Custom logic after orientation has changed
+ }
+ }
+
+ [Fact]
+ public void Orientation_Change_IsSuccessful ()
+ {
+ // Arrange
+ var customView = new CustomView ();
+ var orientationChanged = false;
+ customView.OrientationChanged += (sender, e) => orientationChanged = true;
+
+ // Act
+ customView.Orientation = Orientation.Horizontal;
+
+ // Assert
+ Assert.True (orientationChanged, "OrientationChanged event was not invoked.");
+ Assert.Equal (Orientation.Horizontal, customView.Orientation);
+ }
+
+ [Fact]
+ public void Orientation_Change_OrientationChanging_Set_Cancel_IsCancelled ()
+ {
+ // Arrange
+ var customView = new CustomView ();
+ customView.OrientationChanging += (sender, e) => e.Cancel = true; // Cancel the orientation change
+ var orientationChanged = false;
+ customView.OrientationChanged += (sender, e) => orientationChanged = true;
+
+ // Act
+ customView.Orientation = Orientation.Horizontal;
+
+ // Assert
+ Assert.False (orientationChanged, "OrientationChanged event was invoked despite cancellation.");
+ Assert.Equal (Orientation.Vertical, customView.Orientation); // Assuming Vertical is the default orientation
+ }
+
+ [Fact]
+ public void Orientation_Change_OnOrientationChanging_Return_True_IsCancelled ()
+ {
+ // Arrange
+ var customView = new CustomView ();
+ customView.CancelOnOrientationChanging = true; // Cancel the orientation change
+
+ var orientationChanged = false;
+ customView.OrientationChanged += (sender, e) => orientationChanged = true;
+
+ // Act
+ customView.Orientation = Orientation.Horizontal;
+
+ // Assert
+ Assert.False (orientationChanged, "OrientationChanged event was invoked despite cancellation.");
+ Assert.Equal (Orientation.Vertical, customView.Orientation); // Assuming Vertical is the default orientation
+ }
+
+
+ [Fact]
+ public void OrientationChanging_VirtualMethodCalledBeforeEvent ()
+ {
+ // Arrange
+ var radioGroup = new CustomView ();
+ bool eventCalled = false;
+
+ radioGroup.OrientationChanging += (sender, e) =>
+ {
+ eventCalled = true;
+ Assert.True (radioGroup.OnOrientationChangingCalled, "OnOrientationChanging was not called before the event.");
+ };
+
+ // Act
+ radioGroup.Orientation = Orientation.Horizontal;
+
+ // Assert
+ Assert.True (eventCalled, "OrientationChanging event was not called.");
+ }
+
+ [Fact]
+ public void OrientationChanged_VirtualMethodCalledBeforeEvent ()
+ {
+ // Arrange
+ var radioGroup = new CustomView ();
+ bool eventCalled = false;
+
+ radioGroup.OrientationChanged += (sender, e) =>
+ {
+ eventCalled = true;
+ Assert.True (radioGroup.OnOrientationChangedCalled, "OnOrientationChanged was not called before the event.");
+ };
+
+ // Act
+ radioGroup.Orientation = Orientation.Horizontal;
+
+ // Assert
+ Assert.True (eventCalled, "OrientationChanged event was not called.");
+ }
+}