using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Windows.Forms; using amecs; namespace Ameliorated.ConsoleUtils { public class Menu { public List Choices { get; set; } = new List(); private int TotalOffset = 2; private int? _offset = null; public int Offset { get { if (_offset == null) _offset = BindToOpenFrame ? 5 : 2; return _offset.Value; } set { _offset = value; } } public ConsoleColor? SelectionForeground { get; set; } = ConsoleColor.Green; public ConsoleColor? SelectionBackgound { get; set; } = null; public bool CloseFrame { get; set; } = true; private bool _bindToOpenFrame; public bool BindToOpenFrame { get; set; } = true; private int menuStart; private int pages; private int choicesPerPage; public void Write() { if (Choices.Count < 1) throw new ArgumentException("Property Choices must have at least one choice."); _bindToOpenFrame = ConsoleTUI.OpenFrame != null && BindToOpenFrame; int totalStaticChoices = Choices.Count(x => x.IsStatic || x.IsNextButton || x.IsPreviousButton); if (_bindToOpenFrame) { pages = (int)Math.Ceiling(((double)Choices.Count - totalStaticChoices) / (double)(ConsoleTUI.OpenFrame.DisplayHeight - totalStaticChoices + 1)); choicesPerPage = (ConsoleTUI.OpenFrame.DisplayHeight - totalStaticChoices + 1); } else { pages = (int)Math.Ceiling(((double)Choices.Count - totalStaticChoices) / (double)(((Console.WindowHeight - Console.CursorTop) - totalStaticChoices))); choicesPerPage = (Console.WindowHeight - Console.CursorTop) - totalStaticChoices; } if (_bindToOpenFrame) ConsoleTUI.OpenFrame.Clear(); TotalOffset = _bindToOpenFrame ? ConsoleTUI.OpenFrame.DisplayOffset + Offset : Offset; menuStart = Console.CursorTop; if (_bindToOpenFrame) { Console.SetCursorPosition(TotalOffset, Console.CursorTop); } var fluidChoices = Choices.Where(x => !x.IsStatic && !x.IsNextButton && !x.IsPreviousButton).Take(choicesPerPage); var staticChoices = Choices.Where(x => x.IsStatic || (pages > 1 && x.IsNextButton)); WriteChoiceList(fluidChoices.Concat(staticChoices)); Frame = ConsoleTUI.OpenFrame; if (_bindToOpenFrame && CloseFrame) { try { var offset = ConsoleTUI.OpenFrame.DisplayOffset; if (pages == 1) ConsoleTUI.OpenFrame.Close(); else ConsoleTUI.OpenFrame.Close($"Page 1/{pages}"); Console.WriteLine(new string(' ', offset) + "Use the arrow keys to navigate"); } catch (Exception e) { Console.WriteLine(e); Console.ReadLine(); } } } private void WriteChoiceList(IEnumerable list) { bool selectedWritten = false; Console.SetCursorPosition(0, Console.CursorTop); foreach (MenuItem choice in list) { if (_bindToOpenFrame) { Console.Write( new string(' ', TotalOffset)); if (choice.IsEnabled && !selectedWritten) { choice.WriteSelected(SelectionForeground, SelectionBackgound); selectedWritten = true; Console.WriteLine(); continue; } choice.Write(); } else { Console.Write( new string(' ', TotalOffset)); if (choice.IsEnabled && !selectedWritten) { choice.WriteSelected(SelectionForeground, SelectionBackgound); selectedWritten = true; Console.WriteLine(); continue; } choice.Write(); } Console.WriteLine(); } Console.SetCursorPosition(Console.CursorLeft, Console.CursorTop - 1); } private List ValidChoices = new List(); private ConsoleTUI.Frame Frame; public object Load() { var visCache = Console.CursorVisible; Console.CursorVisible = false; int index = Math.Max(Choices.FindIndex(x => x.IsEnabled), 0); int validIndex = 0; ConsoleKey keyPressed; Console.SetCursorPosition(Console.CursorLeft, menuStart + index); var allFluidChoices = Choices.Where(x => !x.IsStatic && !x.IsNextButton && !x.IsPreviousButton).ToList(); var allStaticChoices = Choices.Where(x => x.IsStatic || x.IsNextButton || x.IsPreviousButton).ToList(); int pageIndex = 1; ConsoleUtils.ClearInputBuffer(); var currentChoices = allFluidChoices.Take(choicesPerPage).Concat(Choices.Where(x => x.IsStatic || (pages > 1 && x.IsNextButton))).ToList(); var currentValidChoices = currentChoices.Where(x => x.IsEnabled).ToList(); while (true) { keyPressed = Console.ReadKey(true).Key; if (keyPressed == ConsoleKey.DownArrow || keyPressed == ConsoleKey.S) { if (validIndex >= currentValidChoices.Count - 1) continue; Console.SetCursorPosition(Math.Max(TotalOffset - 2, 0), Console.CursorTop); Console.Write(" "); currentValidChoices[validIndex].Write(); var fromTop = currentChoices.Skip(index + 1).TakeWhile(x => !x.IsEnabled).Count() + 1; var choice = currentChoices.Skip(index + 1).First(x => x.IsEnabled); Console.SetCursorPosition(TotalOffset, Console.CursorTop + fromTop); choice.WriteSelected(SelectionForeground, SelectionBackgound); index += fromTop; validIndex += 1; } if (keyPressed == ConsoleKey.UpArrow || keyPressed == ConsoleKey.W) { if (!(validIndex > 0)) continue; Console.SetCursorPosition(Math.Max(TotalOffset - 2, 0), Console.CursorTop); Console.Write(" "); currentValidChoices[validIndex].Write(); var fromTop = -currentChoices.Take(index).Reverse().TakeWhile(x => !x.IsEnabled).Count() - 1; var choice = currentChoices.Take(index).Last(x => x.IsEnabled); Console.SetCursorPosition(TotalOffset, Console.CursorTop + fromTop); choice.WriteSelected(SelectionForeground, SelectionBackgound); index += fromTop; validIndex -= 1; } if (keyPressed == ConsoleKey.RightArrow || keyPressed == ConsoleKey.A || keyPressed == ConsoleKey.PageUp || (keyPressed == ConsoleKey.Enter && currentValidChoices[validIndex].IsNextButton)) { if (pageIndex == pages) continue; Frame.Clear(); pageIndex++; var pIndex = pageIndex; var fluidChoices = allFluidChoices.Skip((pIndex - 1) * choicesPerPage).Take(choicesPerPage).ToList(); var staticChoices = allStaticChoices.Where(x => !(pages <= pIndex && x.IsNextButton)); Console.SetCursorPosition(Console.CursorLeft, menuStart); currentChoices = fluidChoices.Concat(staticChoices).ToList(); WriteChoiceList(currentChoices); if (_bindToOpenFrame && CloseFrame) { try { var offset = ConsoleTUI.OpenFrame.DisplayOffset; if (pages == 1) ConsoleTUI.OpenFrame.Close(); else ConsoleTUI.OpenFrame.Close($"Page {pIndex}/{pages}"); Console.WriteLine(new string(' ', offset) + "Use the arrow keys to navigate"); } catch (Exception e) { Console.WriteLine(e); Console.ReadLine(); } } Console.SetCursorPosition(Console.CursorLeft, menuStart); currentValidChoices = currentChoices.Where(x => x.IsEnabled).ToList(); index = 0; validIndex = 0; continue; } if (keyPressed == ConsoleKey.LeftArrow || keyPressed == ConsoleKey.D || keyPressed == ConsoleKey.PageDown || (keyPressed == ConsoleKey.Enter && currentValidChoices[validIndex].IsPreviousButton)) { if (pageIndex == 1) continue; Frame.Clear(); pageIndex--; var pIndex = pageIndex; var fluidChoices = allFluidChoices.Skip((pIndex - 1) * choicesPerPage).Take(choicesPerPage).ToList(); var staticChoices = allStaticChoices.Where(x => !(pIndex <= 1 && x.IsPreviousButton)); Console.SetCursorPosition(Console.CursorLeft, menuStart); currentChoices = fluidChoices.Concat(staticChoices).ToList(); WriteChoiceList(currentChoices); if (_bindToOpenFrame && CloseFrame) { try { var offset = ConsoleTUI.OpenFrame.DisplayOffset; if (pages == 1) ConsoleTUI.OpenFrame.Close(); else ConsoleTUI.OpenFrame.Close($"Page {pIndex}/{pages}"); Console.WriteLine(new string(' ', offset) + "Use the arrow keys to navigate"); } catch (Exception e) { Console.WriteLine(e); Console.ReadLine(); } } Console.SetCursorPosition(Console.CursorLeft, menuStart); currentValidChoices = currentChoices.Where(x => x.IsEnabled).ToList(); index = 0; validIndex = 0; continue; } if (keyPressed == ConsoleKey.Enter) { break; } } if (CloseFrame) Frame.Clear(); Console.CursorVisible = visCache; return currentValidChoices[validIndex].ReturnValue; } private bool NextButtonPresent() { return Choices.Any(x => x.IsNextButton); } private bool PreviousButtonPresent() { return Choices.Any(x => x.IsNextButton); } public class MenuItem { public object ReturnValue { get; set; } public static readonly MenuItem Blank = new MenuItem("", null); public static readonly MenuItem BlankStatic = new MenuItem("", null) { IsStatic = true }; public bool IsNextButton { get; set; } = false; public bool IsPreviousButton { get; set; } = false; public bool IsStatic { get; set; } = false; public string PrimaryText { get; set; } = ""; public string SecondaryText { get; set; } = ""; public MenuItem(string primaryText, object returnValue) { PrimaryText = primaryText; ReturnValue = returnValue; } public bool AddBetweenSpace { get; set; } = true; private ConsoleColor? _primaryTextForeground = null; public ConsoleColor? PrimaryTextForeground { get { if (_primaryTextForeground.HasValue) return _primaryTextForeground; if (IsEnabled) return null; if (Console.ForegroundColor == ConsoleColor.White) return ConsoleColor.DarkGray; if (Console.ForegroundColor == ConsoleColor.White) return ConsoleColor.DarkGray; return null; } set { _primaryTextForeground = value; } } public ConsoleColor? PrimaryTextBackground { get; set; } = null; public ConsoleColor? SecondaryTextForeground { get; set; } = null; public ConsoleColor? SecondaryTextBackground { get; set; } = null; private bool? _isEnabled = null; public bool IsEnabled { get { if (_isEnabled.HasValue) return _isEnabled.Value; if (!String.IsNullOrEmpty(PrimaryText) || !String.IsNullOrEmpty(SecondaryText)) { return true; } return false; } set { _isEnabled = value; } } public bool StretchSelection = false; public void Write() { if (!String.IsNullOrEmpty(PrimaryText)) { ConsoleUtils.SetColor(PrimaryTextForeground, PrimaryTextBackground); Console.Write(PrimaryText); ConsoleUtils.ResetColor(); if (AddBetweenSpace) Console.Write(' '); } if (String.IsNullOrEmpty(SecondaryText)) return; ConsoleUtils.SetColor(SecondaryTextForeground, SecondaryTextBackground); Console.Write(SecondaryText); ConsoleUtils.ResetColor(); } public void WriteSelected(ConsoleColor? foreground, ConsoleColor? background) { Console.SetCursorPosition(Console.CursorLeft - 2, Console.CursorTop); if (!String.IsNullOrEmpty(PrimaryText)) { ConsoleUtils.SetColor(foreground, background); Console.Write("> " + PrimaryText); if (!StretchSelection) ConsoleUtils.ResetColor(); if (AddBetweenSpace) Console.Write(' '); } if (String.IsNullOrEmpty(SecondaryText)) { if (StretchSelection) ConsoleUtils.ResetColor(); return; } if (!String.IsNullOrEmpty(PrimaryText) && !StretchSelection) ConsoleUtils.SetColor(SecondaryTextForeground, SecondaryTextBackground); Console.Write(SecondaryText); ConsoleUtils.ResetColor(); } } } }