/// <summary>
/// Menu.cs
/// Abstract menu class
/// </summary>
public abstract class Menu
{
/// <summary>
/// Menu header UI details
/// </summary>
public class Position
{
public int TopSpacing { get; set; }
public int TopPadding { get; set; }
public int LeftPadding { get; set; }
public int LeftSpacing { get; set; }
public bool LeftCentered { get; set; }
public bool TopCentered { get; set; }
}
public const string MarkerSymbol = "=>";
public List<string> HeaderText { get; set; }
public string EntryTitle { get; set; }
public bool Selected { get; set; }
public Position HeaderPos { get; set; }
public Position EntriesPos { get; set; }
protected internal Menu Parent;
protected internal List<Menu> Children;
protected internal int ChildIndex;
protected internal Menu()
{
Children
= new List
<Menu
>(); HeaderPos
= new Position
(); EntriesPos
= new Position
(); }
protected internal void Select()
{
Selected = true;
}
protected internal void Deselect()
{
Selected = false;
}
protected internal string GetMarker()
{
return Selected
? MarkerSymbol
: new string(' ', MarkerSymbol
.Length); }
public void AddChild(Menu child)
{
child.Parent = this;
Children.Add(child);
}
protected internal string GetEntryString()
{
return $"{this.GetMarker()} {this.EntryTitle}";
}
public void ControlLoop()
{
Console.CursorVisible = false;
do
{
ShowMenu();
ProcessKeyPress();
} while (true);
}
protected internal abstract void Execute();
protected internal abstract void ShowMenu();
protected internal abstract void ProcessKeyPress();
}
/// <summary>
/// VerticalMenu.cs
/// Vertical menu class
/// </summary>
public class VerticalMenu : Menu
{
protected internal override void Execute()
{
ControlLoop();
}
protected internal override void ShowMenu()
{
Console.Clear();
// calculating left positioning for menu header
int leftPosition = (HeaderPos.LeftCentered)
? (Console.WindowWidth / 2) - (HeaderText.Max(x => x.Length) / 2)
: HeaderPos.LeftSpacing;
int topPosition = (HeaderPos.TopCentered)
? (Console.WindowHeight / 2) -
(((HeaderPos.TopPadding > 0) ? HeaderText.Count * 2 * HeaderPos.TopPadding : HeaderText.Count +
((EntriesPos.TopPadding > 0) ? Children.Count * 2 * EntriesPos.TopPadding : Children.Count)) / 2)
: Console.CursorTop + HeaderPos.TopSpacing;
Console.SetCursorPosition(leftPosition, topPosition);
foreach (string line in HeaderText)
{
Console.WriteLine(line);
Console.SetCursorPosition(leftPosition, Console.CursorTop + HeaderPos.TopPadding);
}
// calculating left positioning for menu entries
int totalLeft = Children.Select(x => x.GetEntryString().Length).ToList().Max(x => x);
leftPosition = (EntriesPos.LeftCentered)
? (Console.WindowWidth / 2) -
(totalLeft / 2)
: EntriesPos.LeftSpacing;
Console.SetCursorPosition(leftPosition, Console.CursorTop + EntriesPos.TopSpacing);
foreach (var child in Children)
{
Console.WriteLine(child.GetEntryString());
Console.SetCursorPosition(leftPosition, Console.CursorTop + EntriesPos.TopPadding);
}
}
protected internal override void ProcessKeyPress()
{
var key = Console.ReadKey();
switch (key.Key)
{
case ConsoleKey.UpArrow:
MoveMarkerUp();
break;
case ConsoleKey.DownArrow:
MoveMarkerDown();
break;
case ConsoleKey.Enter:
ExecuteEntry();
break;
case ConsoleKey.Escape:
TryExitConsole();
break;
}
}
private void MoveMarkerUp()
{
ChildIndex = (ChildIndex - 1 >= 0) ? ChildIndex -= 1 : Children.Count - 1;
Children.ForEach(x => x.Deselect());
Children[ChildIndex].Select();
}
private void MoveMarkerDown()
{
ChildIndex = (ChildIndex + 1 < Children.Count) ? ChildIndex += 1 : 0;
Children.ForEach(x => x.Deselect());
Children[ChildIndex].Select();
}
private void ExecuteEntry()
{
if (ChildIndex >= 0 && ChildIndex < Children.Count)
{
Menu menu = Children[ChildIndex];
if (menu
?.GetType() != typeof(Entry
) && menu
?.Children.Count == 0) {
menu = this.Parent;
}
menu?.Execute();
}
}
private void TryExitConsole()
{
if (this.Parent == null)
Environment.Exit(0);
}
}
/// <summary>
/// HorizontalMenu.cs
/// Horizontal menu class
/// </summary>
public class HorizontalMenu : Menu
{
protected internal override void Execute()
{
ControlLoop();
}
protected internal override void ShowMenu()
{
Console.Clear();
// calculating left positioning for menu header
int leftPosition = (HeaderPos.LeftCentered)
? (Console.WindowWidth / 2) - (HeaderText.Max(x => x.Length) / 2)
: HeaderPos.LeftSpacing;
int topPosition = (HeaderPos.TopCentered)
? (Console.WindowHeight / 2) -
(((HeaderPos.TopPadding > 0) ? HeaderText.Count * 2 * HeaderPos.TopPadding : HeaderText.Count +
((EntriesPos.TopSpacing > 0) ? EntriesPos.TopSpacing + 1 : 1)) / 2)
: Console.CursorTop + HeaderPos.TopSpacing;
Console.SetCursorPosition(leftPosition, topPosition);
foreach (string line in HeaderText)
{
Console.WriteLine(line);
Console.SetCursorPosition(leftPosition, Console.CursorTop + HeaderPos.TopPadding);
}
// calculating left positioning for menu entries
int totalEntriesLeftSpace = Children.Select(x => x.GetEntryString().Length + EntriesPos.LeftPadding)
.ToList().Sum(x => x) - EntriesPos.LeftPadding;
topPosition = Console.CursorTop + EntriesPos.TopSpacing;
leftPosition = (EntriesPos.LeftCentered)
? (Console.WindowWidth / 2) - (totalEntriesLeftSpace / 2)
: EntriesPos.LeftSpacing;
foreach (var entry in Children)
{
Console.SetCursorPosition(leftPosition, topPosition);
Console.WriteLine(entry.GetEntryString());
leftPosition += MarkerSymbol.Length + entry.EntryTitle.Length + EntriesPos.LeftPadding;
}
}
protected internal override void ProcessKeyPress()
{
var key = Console.ReadKey();
switch (key.Key)
{
case ConsoleKey.LeftArrow:
MoveMarkerLeft();
break;
case ConsoleKey.RightArrow:
MoveMarkerRight();
break;
case ConsoleKey.Enter:
ExecuteEntry();
break;
case ConsoleKey.Escape:
TryExitConsole();
break;
}
}
private void MoveMarkerLeft()
{
ChildIndex = (ChildIndex - 1 >= 0) ? ChildIndex -= 1 : Children.Count - 1;
Children.ForEach(x => x.Deselect());
Children[ChildIndex].Select();
}
private void MoveMarkerRight()
{
ChildIndex = (ChildIndex + 1 < Children.Count) ? ChildIndex += 1 : 0;
Children.ForEach(x => x.Deselect());
Children[ChildIndex].Select();
}
private void ExecuteEntry()
{
if (ChildIndex >= 0 && ChildIndex < Children.Count)
{
Menu menu = Children[ChildIndex];
if (menu
?.GetType() != typeof(Entry
) && menu
?.Children.Count == 0) {
menu = this.Parent;
}
menu?.Execute();
}
}
private void TryExitConsole()
{
if (this.Parent == null)
Environment.Exit(0);
}
}
/// <summary>
/// Entry.cs
/// Menu entry class
/// </summary>
public class Entry : Menu
{
public Action Event { get; set; }
protected internal override void Execute()
{
Event?.Invoke();
}
protected internal override void ShowMenu() { }
protected internal override void ProcessKeyPress() { }
}