Skip to content

Commit

Permalink
Merge pull request gui-cs#3625 from tig/v2-IOrientation
Browse files Browse the repository at this point in the history
Adds `IOrientation` and `OrientationHelper` - opinionated changing/changed event pattern
  • Loading branch information
tig authored Aug 2, 2024
2 parents a0e639d + 5768add commit 94fdcbe
Show file tree
Hide file tree
Showing 16 changed files with 997 additions and 426 deletions.
42 changes: 42 additions & 0 deletions Terminal.Gui/View/Orientation/IOrientation.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
File renamed without changes.
138 changes: 138 additions & 0 deletions Terminal.Gui/View/Orientation/OrientationHelper.cs
Original file line number Diff line number Diff line change
@@ -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) =&gt; OrientationChanging?.Invoke (this, e);
/// _orientationHelper.OrientationChanged += (sender, e) =&gt; OrientationChanged?.Invoke (this, e);
/// }
///
/// public Orientation Orientation
/// {
/// get =&gt; _orientationHelper.Orientation;
/// set =&gt; _orientationHelper.Orientation = value;
/// }
///
/// public event EventHandler&lt;CancelEventArgs&lt;Orientation&gt;&gt; OrientationChanging;
/// public event EventHandler&lt;EventArgs&lt;Orientation&gt;&gt; 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;
}
57 changes: 49 additions & 8 deletions Terminal.Gui/Views/Bar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ([]) { }

Expand All @@ -24,6 +26,10 @@ public Bar (IEnumerable<Shortcut> shortcuts)
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)
Expand All @@ -46,7 +52,7 @@ public override void SetBorderStyle (LineStyle value)
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
Expand All @@ -58,15 +64,26 @@ public override void SetBorderStyle (LineStyle value)
/// 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;

Expand Down Expand Up @@ -224,4 +241,28 @@ internal override void OnLayoutStarted (LayoutEventArgs args)
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;
}
}
47 changes: 32 additions & 15 deletions Terminal.Gui/Views/Line.cs
Original file line number Diff line number Diff line change
@@ -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
{
_orientation = value;
get => _orientationHelper.Orientation;
set => _orientationHelper.Orientation = value;
}

/// <inheritdoc/>
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;

switch (Orientation)
{
case Orientation.Horizontal:
Height = 1;
/// <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)
{
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)
Expand Down
19 changes: 0 additions & 19 deletions Terminal.Gui/Views/OrientationEventArgs.cs

This file was deleted.

Loading

0 comments on commit 94fdcbe

Please sign in to comment.