mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Merge pull request #3625 from tig/v2-IOrientation
Adds `IOrientation` and `OrientationHelper` - opinionated changing/changed event pattern
This commit is contained in:
42
Terminal.Gui/View/Orientation/IOrientation.cs
Normal file
42
Terminal.Gui/View/Orientation/IOrientation.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
namespace Terminal.Gui;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Implement this interface to provide orientation support.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see cref="OrientationHelper"/> for a helper class that implements this interface.
|
||||
/// </remarks>
|
||||
public interface IOrientation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the orientation of the View.
|
||||
/// </summary>
|
||||
Orientation Orientation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when <see cref="Orientation"/> is changing. Can be cancelled.
|
||||
/// </summary>
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="Orientation"/> is changing.
|
||||
/// </summary>
|
||||
/// <param name="currentOrientation">The current orientation.</param>
|
||||
/// <param name="newOrientation">The new orientation.</param>
|
||||
/// <returns><see langword="true"/> to cancel the change.</returns>
|
||||
public bool OnOrientationChanging (Orientation currentOrientation, Orientation newOrientation) { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when <see cref="Orientation"/> has changed.
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="Orientation"/> has been changed.
|
||||
/// </summary>
|
||||
/// <param name="newOrientation"></param>
|
||||
/// <returns></returns>
|
||||
public void OnOrientationChanged (Orientation newOrientation) { return; }
|
||||
}
|
||||
138
Terminal.Gui/View/Orientation/OrientationHelper.cs
Normal file
138
Terminal.Gui/View/Orientation/OrientationHelper.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for implementing <see cref="IOrientation"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Implements the standard pattern for changing/changed events.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// 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
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class OrientationHelper
|
||||
{
|
||||
private Orientation _orientation;
|
||||
private readonly IOrientation _owner;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OrientationHelper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">Specifies the object that owns this helper instance and implements <see cref="IOrientation"/>.</param>
|
||||
public OrientationHelper (IOrientation owner) { _owner = owner; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the orientation of the View.
|
||||
/// </summary>
|
||||
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<Orientation> 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the orientation is changing. This is cancelable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Views that implement <see cref="IOrientation"/> should raise <see cref="IOrientation.OrientationChanging"/>
|
||||
/// after the orientation has changed
|
||||
/// (<code>_orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);</code>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This event will be raised after the <see cref="IOrientation.OnOrientationChanging"/> method is called (assuming
|
||||
/// it was not canceled).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the orientation has changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Views that implement <see cref="IOrientation"/> should raise <see cref="IOrientation.OrientationChanged"/>
|
||||
/// after the orientation has changed
|
||||
/// (<code>_orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);</code>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This event will be raised after the <see cref="IOrientation.OnOrientationChanged"/> method is called.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
|
||||
}
|
||||
@@ -11,8 +11,10 @@ namespace Terminal.Gui;
|
||||
/// align them in a specific order.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class Bar : View
|
||||
public class Bar : View, IOrientation, IDesignable
|
||||
{
|
||||
private readonly OrientationHelper _orientationHelper;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Orientation"/> for this <see cref="Bar"/>. The default is
|
||||
@@ -58,16 +64,27 @@ public class Bar : View
|
||||
/// Vertical orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from left to right.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => _orientation;
|
||||
set
|
||||
{
|
||||
_orientation = value;
|
||||
SetNeedsLayout ();
|
||||
}
|
||||
get => _orientationHelper.Orientation;
|
||||
set => _orientationHelper.Orientation = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
|
||||
|
||||
/// <summary>Called when <see cref="Orientation"/> has changed.</summary>
|
||||
/// <param name="newOrientation"></param>
|
||||
public void OnOrientationChanged (Orientation newOrientation)
|
||||
{
|
||||
SetNeedsLayout ();
|
||||
}
|
||||
#endregion
|
||||
|
||||
private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd;
|
||||
|
||||
/// <summary>
|
||||
@@ -224,4 +241,28 @@ public class Bar : View
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,60 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>Draws a single line using the <see cref="LineStyle"/> specified by <see cref="View.BorderStyle"/>.</summary>
|
||||
public class Line : View
|
||||
public class Line : View, IOrientation
|
||||
{
|
||||
private readonly OrientationHelper _orientationHelper;
|
||||
|
||||
/// <summary>Constructs a Line object.</summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => _orientation;
|
||||
set
|
||||
get => _orientationHelper.Orientation;
|
||||
set => _orientationHelper.Orientation = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
|
||||
|
||||
/// <summary>Called when <see cref="Orientation"/> has changed.</summary>
|
||||
/// <param name="newOrientation"></param>
|
||||
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
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void SetBorderStyle (LineStyle value)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary><see cref="EventArgs"/> for <see cref="Orientation"/> events.</summary>
|
||||
public class OrientationEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>Constructs a new instance.</summary>
|
||||
/// <param name="orientation">the new orientation</param>
|
||||
public OrientationEventArgs (Orientation orientation)
|
||||
{
|
||||
Orientation = orientation;
|
||||
Cancel = false;
|
||||
}
|
||||
|
||||
/// <summary>If set to true, the orientation change operation will be canceled, if applicable.</summary>
|
||||
public bool Cancel { get; set; }
|
||||
|
||||
/// <summary>The new orientation.</summary>
|
||||
public Orientation Orientation { get; set; }
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.</summary>
|
||||
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<string> _radioLabels = [];
|
||||
private int _selected;
|
||||
private readonly OrientationHelper _orientationHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RadioGroup"/> 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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Orientation"/> for this <see cref="RadioGroup"/>. The default is
|
||||
/// <see cref="Orientation.Vertical"/>.
|
||||
/// </summary>
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => _orientation;
|
||||
set => OnOrientationChanged (value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="View.HotKey"/> for details on how HotKeys work.
|
||||
@@ -323,44 +322,49 @@ public class RadioGroup : View, IDesignable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called when the view orientation has changed. Invokes the <see cref="OrientationChanged"/> event.</summary>
|
||||
/// <param name="newOrientation"></param>
|
||||
/// <returns>True of the event was cancelled.</returns>
|
||||
public virtual bool OnOrientationChanged (Orientation newOrientation)
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Orientation"/> for this <see cref="RadioGroup"/>. The default is
|
||||
/// <see cref="Orientation.Vertical"/>.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
|
||||
|
||||
/// <summary>Called when <see cref="Orientation"/> has changed.</summary>
|
||||
/// <param name="newOrientation"></param>
|
||||
public void OnOrientationChanged (Orientation newOrientation)
|
||||
{
|
||||
SetupKeyBindings ();
|
||||
SetContentSize ();
|
||||
}
|
||||
|
||||
#endregion IOrientation
|
||||
|
||||
// TODO: This should be cancelable
|
||||
/// <summary>Called whenever the current selected item changes. Invokes the <see cref="SelectedItemChanged"/> event.</summary>
|
||||
/// <param name="selectedItem"></param>
|
||||
/// <param name="previousSelectedItem"></param>
|
||||
public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
|
||||
{
|
||||
{
|
||||
if (_selected == selectedItem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selected = selectedItem;
|
||||
SelectedItemChanged?.Invoke (this, new (selectedItem, previousSelectedItem));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the view orientation has changed. Can be cancelled by setting
|
||||
/// <see cref="OrientationEventArgs.Cancel"/> to true.
|
||||
/// </summary>
|
||||
public event EventHandler<OrientationEventArgs> OrientationChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <inheritdoc/>
|
||||
public bool EnableForDesign ()
|
||||
{
|
||||
RadioLabels = new [] { "Option _1", "Option _2", "Option _3" };
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace Terminal.Gui;
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// Displays a command, help text, and a key binding. When the key specified by <see cref="Key"/> is pressed, the command will be invoked. Useful for
|
||||
/// Displays a command, help text, and a key binding. When the key specified by <see cref="Key"/> is pressed, the
|
||||
/// command will be invoked. Useful for
|
||||
/// displaying a command in <see cref="Bar"/> such as a
|
||||
/// menu, toolbar, or status bar.
|
||||
/// </summary>
|
||||
@@ -12,12 +10,13 @@ namespace Terminal.Gui;
|
||||
/// <para>
|
||||
/// The following user actions will invoke the <see cref="Command.Accept"/>, causing the
|
||||
/// <see cref="View.Accept"/> event to be fired:
|
||||
/// - Clicking on the <see cref="Shortcut"/>.
|
||||
/// - Pressing the key specified by <see cref="Key"/>.
|
||||
/// - Pressing the HotKey specified by <see cref="CommandView"/>.
|
||||
/// - Clicking on the <see cref="Shortcut"/>.
|
||||
/// - Pressing the key specified by <see cref="Key"/>.
|
||||
/// - Pressing the HotKey specified by <see cref="CommandView"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If <see cref="KeyBindingScope"/> is <see cref="KeyBindingScope.Application"/>, <see cref="Key"/> will invoked <see cref="Command.Accept"/>
|
||||
/// If <see cref="KeyBindingScope"/> is <see cref="KeyBindingScope.Application"/>, <see cref="Key"/> will invoked
|
||||
/// <see cref="Command.Accept"/>
|
||||
/// command regardless of what View has focus, enabling an application-wide keyboard shortcut.
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -37,8 +36,10 @@ namespace Terminal.Gui;
|
||||
/// If the <see cref="Key"/> is <see cref="Key.Empty"/>, the <see cref="Key"/> text is not displayed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class Shortcut : View
|
||||
public class Shortcut : View, IOrientation, IDesignable
|
||||
{
|
||||
private readonly OrientationHelper _orientationHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="Shortcut"/>.
|
||||
/// </summary>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="Shortcut"/>.
|
||||
/// </summary>
|
||||
public Shortcut () : this (Key.Empty, string.Empty, null) { }
|
||||
|
||||
private Orientation _orientation = Orientation.Horizontal;
|
||||
#region IOrientation members
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Orientation"/> for this <see cref="Shortcut"/>. The default is
|
||||
/// <see cref="Orientation.Horizontal"/>, which is ideal for status bar, menu bar, and tool bar items If set to
|
||||
/// <see cref="Orientation.Vertical"/>,
|
||||
/// the Shortcut will be configured for vertical layout, which is ideal for menu items.
|
||||
/// Gets or sets the <see cref="Orientation"/> for this <see cref="Bar"/>. The default is
|
||||
/// <see cref="Orientation.Horizontal"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Horizontal orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from right to
|
||||
/// left
|
||||
/// Vertical orientation arranges the command, help, and key parts of each <see cref="Shortcut"/>s from left to
|
||||
/// right.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
|
||||
|
||||
/// <summary>Called when <see cref="Orientation"/> has changed.</summary>
|
||||
/// <param name="newOrientation"></param>
|
||||
public void OnOrientationChanged (Orientation newOrientation)
|
||||
{
|
||||
// TODO: Determine what, if anything, is opinionated about the orientation.
|
||||
SetNeedsLayout ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast;
|
||||
|
||||
/// <summary>
|
||||
@@ -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<string> 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
|
||||
|
||||
/// <summary>
|
||||
/// The subview that displays the help text for the command. Internal for unit testing.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the help text displayed in the middle of the Shortcut. Identical in function to <see cref="HelpText"/>
|
||||
/// .
|
||||
/// </summary>
|
||||
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<string> 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
|
||||
|
||||
/// <summary>
|
||||
/// The subview that displays the help text for the command. Internal for unit testing.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the help text displayed in the middle of the Shortcut. Identical in function to <see cref="HelpText"/>
|
||||
/// .
|
||||
/// </summary>
|
||||
public override string Text
|
||||
{
|
||||
get => HelpView?.Text;
|
||||
set
|
||||
{
|
||||
HelpView.Text = value;
|
||||
ShowHide ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the help text displayed in the middle of the Shortcut.
|
||||
/// </summary>
|
||||
public string HelpText
|
||||
{
|
||||
get => HelpView?.Text;
|
||||
set
|
||||
{
|
||||
if (HelpView is {})
|
||||
{
|
||||
HelpView.Text = value;
|
||||
ShowHide ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Help
|
||||
|
||||
#region Key
|
||||
|
||||
private Key _key = Key.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Key"/> that will be bound to the <see cref="Command.Accept"/> command.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scope for the key binding for how <see cref="Key"/> is bound to <see cref="Command"/>.
|
||||
/// </summary>
|
||||
public KeyBindingScope KeyBindingScope
|
||||
{
|
||||
get => _keyBindingScope;
|
||||
set
|
||||
{
|
||||
_keyBindingScope = value;
|
||||
|
||||
UpdateKeyBinding ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subview that displays the key. Internal for unit testing.
|
||||
/// </summary>
|
||||
|
||||
internal View KeyView { get; } = new ();
|
||||
|
||||
private int _minimumKeyTextSize;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum size of the key text. Useful for aligning the key text with other <see cref="Shortcut"/>s.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Called when the <see cref="Command.Accept"/> 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).
|
||||
/// </summary>
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the help text displayed in the middle of the Shortcut.
|
||||
/// </summary>
|
||||
public string HelpText
|
||||
{
|
||||
get => HelpView?.Text;
|
||||
set
|
||||
{
|
||||
if (HelpView is {})
|
||||
{
|
||||
HelpView.Text = value;
|
||||
ShowHide ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Help
|
||||
|
||||
#region Key
|
||||
|
||||
private Key _key = Key.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Key"/> that will be bound to the <see cref="Command.Accept"/> command.
|
||||
/// </summary>
|
||||
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 { })
|
||||
/// <summary>
|
||||
/// Gets or sets the scope for the key binding for how <see cref="Key"/> is bound to <see cref="Command"/>.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the subview that displays the key. Internal for unit testing.
|
||||
/// </summary>
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the
|
||||
/// mouse.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note, the <see cref="View.Accept"/> event is fired first, and if cancelled, the event will not be invoked.
|
||||
/// </remarks>
|
||||
[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))
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum size of the key text. Useful for aligning the key text with other <see cref="Shortcut"/>s.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Called when the <see cref="Command.Accept"/> 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).
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the
|
||||
/// mouse.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note, the <see cref="View.Accept"/> event is fired first, and if cancelled, the event will not be invoked.
|
||||
/// </remarks>
|
||||
[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
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ColorScheme ColorScheme
|
||||
{
|
||||
get => base.ColorScheme;
|
||||
set
|
||||
{
|
||||
base.ColorScheme = value;
|
||||
SetColors ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool OnEnter (View view)
|
||||
{
|
||||
base.ColorScheme = value;
|
||||
SetColors ();
|
||||
_lastFocusedView = view;
|
||||
|
||||
return base.OnEnter (view);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
internal void SetColors ()
|
||||
{
|
||||
// Border should match superview.
|
||||
Border.ColorScheme = SuperView?.ColorScheme;
|
||||
|
||||
if (HasFocus)
|
||||
/// <inheritdoc/>
|
||||
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
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool EnableForDesign ()
|
||||
{
|
||||
Title = "_Shortcut";
|
||||
HelpText = "Shortcut help";
|
||||
Key = Key.F1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
/// <inheritdoc/>
|
||||
public override bool OnEnter (View view)
|
||||
{
|
||||
SetColors ();
|
||||
_lastFocusedView = view;
|
||||
|
||||
return base.OnEnter (view);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool OnLeave (View view)
|
||||
{
|
||||
SetColors ();
|
||||
_lastFocusedView = this;
|
||||
|
||||
return base.OnLeave (view);
|
||||
}
|
||||
|
||||
#endregion Focus
|
||||
|
||||
/// <inheritdoc/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class Slider : Slider<object>
|
||||
/// keyboard or mouse.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class Slider<T> : View
|
||||
public class Slider<T> : View, IOrientation
|
||||
{
|
||||
private readonly SliderConfiguration _config = new ();
|
||||
|
||||
@@ -31,6 +31,8 @@ public class Slider<T> : View
|
||||
// Options
|
||||
private List<SliderOption<T>> _options;
|
||||
|
||||
private OrientationHelper _orientationHelper;
|
||||
|
||||
#region Initialize
|
||||
|
||||
private void SetInitialProperties (
|
||||
@@ -45,11 +47,13 @@ public class Slider<T> : View
|
||||
|
||||
_options = options ?? new List<SliderOption<T>> ();
|
||||
|
||||
_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<T> : View
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Slider Orientation. <see cref="Gui.Orientation"></see></summary>
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Orientation"/>. The default is <see cref="Orientation.Horizontal"/>.
|
||||
/// </summary>
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => _config._sliderOrientation;
|
||||
set => OnOrientationChanged (value);
|
||||
get => _orientationHelper.Orientation;
|
||||
set => _orientationHelper.Orientation = value;
|
||||
}
|
||||
|
||||
#region IOrientation members
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
|
||||
/// <summary>Legends Orientation. <see cref="Gui.Orientation"></see></summary>
|
||||
public Orientation LegendsOrientation
|
||||
{
|
||||
@@ -309,43 +346,6 @@ public class Slider<T> : View
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the slider orientation has changed. Can be cancelled by setting
|
||||
/// <see cref="OrientationEventArgs.Cancel"/> to true.
|
||||
/// </summary>
|
||||
public event EventHandler<OrientationEventArgs> OrientationChanged;
|
||||
|
||||
/// <summary>Called when the slider orientation has changed. Invokes the <see cref="OrientationChanged"/> event.</summary>
|
||||
/// <param name="newOrientation"></param>
|
||||
/// <returns>True of the event was cancelled.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Event raised when the slider option/s changed. The dictionary contains: key = option index, value = T</summary>
|
||||
public event EventHandler<SliderEventArgs<T>> OptionsChanged;
|
||||
|
||||
@@ -1738,7 +1738,7 @@ public class Slider<T> : View
|
||||
|
||||
internal bool Select ()
|
||||
{
|
||||
SetFocusedOption();
|
||||
SetFocusedOption ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
public class StatusBar : Bar
|
||||
public class StatusBar : Bar, IDesignable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public StatusBar () : this ([]) { }
|
||||
@@ -74,4 +74,74 @@ public class StatusBar : Bar
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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}"); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ public class ExpanderButton : Button
|
||||
/// <returns>True of the event was cancelled.</returns>
|
||||
protected virtual bool OnOrientationChanging (Orientation newOrientation)
|
||||
{
|
||||
var args = new OrientationEventArgs (newOrientation);
|
||||
var args = new CancelEventArgs<Orientation> (in _orientation, ref newOrientation);
|
||||
OrientationChanging?.Invoke (this, args);
|
||||
|
||||
if (!args.Cancel)
|
||||
@@ -103,10 +103,9 @@ public class ExpanderButton : Button
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the orientation has changed. Can be cancelled by setting
|
||||
/// <see cref="OrientationEventArgs.Cancel"/> to true.
|
||||
/// Fired when the orientation has changed. Can be cancelled.
|
||||
/// </summary>
|
||||
public event EventHandler<OrientationEventArgs> OrientationChanging;
|
||||
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
|
||||
/// <summary>
|
||||
/// The glyph to display when the view is collapsed.
|
||||
|
||||
@@ -30,9 +30,10 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="[2024.2.0,)" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="[17.10,18)" />
|
||||
<PackageReference Include="ReportGenerator" Version="[5.3.7,6)" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="[21.0.22,22)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0-release-24352-06" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="ReportGenerator" Version="5.3.8" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="21.0.26" />
|
||||
<PackageReference Include="xunit" Version="[2.9.0,3)" />
|
||||
<PackageReference Include="Xunit.Combinatorial" Version="[1.6.24,2)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="[2.8.2,3)">
|
||||
@@ -58,6 +59,9 @@
|
||||
<Using Include="Terminal.Gui" />
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="View\Orientation\" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="FineCodeCoverage">
|
||||
<Enabled>
|
||||
False
|
||||
|
||||
107
UnitTests/View/Orientation/OrientationHelperTests.cs
Normal file
107
UnitTests/View/Orientation/OrientationHelperTests.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Moq;
|
||||
|
||||
namespace Terminal.Gui.ViewTests.OrientationTests;
|
||||
|
||||
public class OrientationHelperTests
|
||||
{
|
||||
[Fact]
|
||||
public void Orientation_Set_NewValue_InvokesChangingAndChangedEvents ()
|
||||
{
|
||||
// Arrange
|
||||
Mock<IOrientation> mockIOrientation = new Mock<IOrientation> ();
|
||||
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<IOrientation> mockIOrientation = new Mock<IOrientation> ();
|
||||
var onChangingOverrideCalled = false;
|
||||
var onChangedOverrideCalled = false;
|
||||
|
||||
mockIOrientation.Setup (x => x.OnOrientationChanging (It.IsAny<Orientation> (), It.IsAny<Orientation> ()))
|
||||
.Callback (() => onChangingOverrideCalled = true)
|
||||
.Returns (false); // Ensure it doesn't cancel the change
|
||||
|
||||
mockIOrientation.Setup (x => x.OnOrientationChanged (It.IsAny<Orientation> ()))
|
||||
.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<IOrientation> mockIOrientation = new Mock<IOrientation> ();
|
||||
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<IOrientation> mockIOrientation = new Mock<IOrientation> ();
|
||||
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<IOrientation> mockIOrientation = new Mock<IOrientation> ();
|
||||
|
||||
mockIOrientation.Setup (x => x.OnOrientationChanging (It.IsAny<Orientation> (), It.IsAny<Orientation> ()))
|
||||
.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
|
||||
}
|
||||
}
|
||||
136
UnitTests/View/Orientation/OrientationTests.cs
Normal file
136
UnitTests/View/Orientation/OrientationTests.cs
Normal file
@@ -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<CancelEventArgs<Orientation>> OrientationChanging;
|
||||
public event EventHandler<EventArgs<Orientation>> 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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user