You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
9.2 KiB
268 lines
9.2 KiB
4 weeks ago
|
using System.Diagnostics.Contracts;
|
||
|
using System.IO.Pipes;
|
||
|
using Microsoft.VisualBasic;
|
||
|
using Mindmagma.Curses;
|
||
|
using Newtonsoft.Json.Linq;
|
||
|
|
||
|
namespace SCI.CursesWrapper;
|
||
|
|
||
|
public class Helper {
|
||
|
/// <summary>
|
||
|
/// Create a centered NCurses window on the specified screen
|
||
|
/// </summary>
|
||
|
/// <param name="screen">Parent screen to attach window to</param>
|
||
|
/// <param name="width">Width of columns of window</param>
|
||
|
/// <param name="height">Height of rows of window</param>
|
||
|
/// <param name="borders">If true, shows default borders around window</param>
|
||
|
/// <returns>nint - Pointer of new window</returns>
|
||
|
/// <exception cref="ArgumentOutOfRangeException">Throws exception if window is outside of screen bounds</exception>
|
||
|
public static nint CreateCenteredWindow (nint screen, int width, int height, bool borders = true) {
|
||
|
NCurses.GetMaxYX (screen, out int screenHeight, out int screenWidth);
|
||
|
|
||
|
int originY = (screenHeight / 2) - (height / 2);
|
||
|
int originX = (screenWidth / 2) - (width / 2);
|
||
|
|
||
|
if (originX + width > screenWidth) throw new ArgumentOutOfRangeException ("width");
|
||
|
if (originY + height > screenHeight) throw new ArgumentOutOfRangeException ("height");
|
||
|
|
||
|
if (originX < 0) throw new ArgumentOutOfRangeException ("originX");
|
||
|
if (originY < 0) throw new ArgumentOutOfRangeException ("originY");
|
||
|
|
||
|
nint window = NCurses.NewWindow (height, width, originY, originX);
|
||
|
if (borders) NCurses.Box (window, (char) 0, (char) 0);
|
||
|
|
||
|
return window;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Prints text horizontally centered to window (on a specific line);
|
||
|
/// </summary>
|
||
|
/// <param name="window">Parent window to print into</param>
|
||
|
/// <param name="text">Text to display</param>
|
||
|
/// <param name="y">Y posititon of the text</param>
|
||
|
public static void PrintHCenteredText (nint window, string text, int y = 0) {
|
||
|
NCurses.GetMaxYX (window, out int _, out int windowWidth);
|
||
|
|
||
|
var x = windowWidth / 2 - text.Length / 2;
|
||
|
NCurses.MoveWindowAddString (window, y, x, text);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class MessageBox {
|
||
|
public enum MessageBoxButtons {
|
||
|
OK,
|
||
|
YesNo,
|
||
|
RetryCancel,
|
||
|
RetryIgnoreCancel
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Shows a message box with specified text and returns the selected button from zero index
|
||
|
/// </summary>
|
||
|
/// <param name="screen">Parent screen to show message box on</param>
|
||
|
/// <param name="text">Text to show</param>
|
||
|
/// <param name="buttons">Type of buttons to show</param>
|
||
|
/// <returns>Button pressed by user, zero indexed</returns>
|
||
|
public static int Show (nint screen, string text, MessageBoxButtons buttons = MessageBoxButtons.OK) {
|
||
|
NCurses.GetMaxYX (screen, out int screenHeight, out int screenWidth);
|
||
|
|
||
|
// Set static box width, if width is larger than screen, reduce to screen size,
|
||
|
// then calculate the message box and inner pad origin X coords
|
||
|
var boxWidth = 50;
|
||
|
if (boxWidth + 3 >= screenWidth) boxWidth = screenWidth - 3;
|
||
|
int originX = (screenWidth / 2) - (boxWidth / 2);
|
||
|
var padOriginX = originX + 2; // Add one for the border and for a single character padding
|
||
|
|
||
|
// Calculate sizes for the inner pad element
|
||
|
var padWidth = boxWidth - 4; // Add four to the total box width due to the two character padding on both sides
|
||
|
var totalTextLines = (int) Math.Ceiling ((double) text.Length / padWidth); // Get total amount of lines required to hold all text limited by box width
|
||
|
var padHeight = totalTextLines;
|
||
|
if (padHeight + 8 > screenHeight) { // If height required to show all text lines plus padding is larger than screen, reduce size of message box
|
||
|
padHeight = screenHeight - 8;
|
||
|
}
|
||
|
|
||
|
// Now calculate the height and Y origin of the text box and inner pad
|
||
|
var boxHeight = padHeight + 6; // Add five to the pad height due to the title bar and two character padding top and bottom
|
||
|
if (boxHeight + 2 > screenHeight) boxHeight = screenHeight - 2;
|
||
|
int originY = (screenHeight / 2) - (boxHeight / 2);
|
||
|
var padOriginY = originY + 3; // Add one for the border, one for the title and one for a single character padding
|
||
|
|
||
|
//if (text.Length > (totalTextRows * textCols)) text = text.Substring (0, (totalTextRows * textCols) - 1) + "$";
|
||
|
|
||
|
// Create message box and inner Pad
|
||
|
var msgBoxWindow = Helper.CreateCenteredWindow (screen, boxWidth, boxHeight, true);
|
||
|
var msgTextPad = NCurses.NewPad (padHeight, padWidth);
|
||
|
|
||
|
NCurses.Refresh ();
|
||
|
|
||
|
// Title, currently not in use
|
||
|
Helper.PrintHCenteredText (msgBoxWindow, "Info", 1);
|
||
|
NCurses.WindowRefresh (msgBoxWindow);
|
||
|
|
||
|
NCurses.WindowAddString (msgTextPad, text);
|
||
|
|
||
|
NCurses.PadRefresh (msgTextPad, 0, 0, padOriginY, padOriginX, padOriginY + padHeight, padOriginX + padWidth);
|
||
|
|
||
|
return InputHandler (msgBoxWindow, buttons);
|
||
|
}
|
||
|
|
||
|
private static List<string> GetStringsFromButtonEnum (MessageBoxButtons buttons) {
|
||
|
List<string> buttonStrings;
|
||
|
|
||
|
switch (buttons) {
|
||
|
case MessageBoxButtons.OK:
|
||
|
buttonStrings = new List<string> () { "OK" };
|
||
|
break;
|
||
|
case MessageBoxButtons.YesNo:
|
||
|
buttonStrings = new List<string> () { "Yes", "No" };
|
||
|
break;
|
||
|
case MessageBoxButtons.RetryCancel:
|
||
|
buttonStrings = new List<string> () { "Retry", "Cancel" };
|
||
|
break;
|
||
|
case MessageBoxButtons.RetryIgnoreCancel:
|
||
|
buttonStrings = new List<string> () { "Retry", "Ignore", "Cancel" };
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception ("Unknown button layout for message box");
|
||
|
}
|
||
|
|
||
|
return buttonStrings;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Draws buttons specified by the buttons type
|
||
|
/// </summary>
|
||
|
/// <param name="window">Window to draw buttons on</param>
|
||
|
/// <param name="buttons">Buttons to display</param>
|
||
|
private static void DrawButtons (nint window, MessageBoxButtons buttons, int active = 0) {
|
||
|
NCurses.GetMaxYX (window, out int windowHeight, out int windowWidth);
|
||
|
|
||
|
var buttonStrings = GetStringsFromButtonEnum (buttons);
|
||
|
var totalStringLength = buttonStrings.Count - 1;
|
||
|
foreach (var str in buttonStrings) totalStringLength += str.Length + 2;
|
||
|
|
||
|
NCurses.WindowMove (window, windowHeight - 2, (windowWidth / 2) - (totalStringLength / 2));
|
||
|
|
||
|
var index = 0;
|
||
|
foreach (var str in buttonStrings) {
|
||
|
if (index == active) {
|
||
|
NCurses.WindowAddString (window, $"[{str}]");
|
||
|
} else {
|
||
|
NCurses.WindowAddString (window, $" {str} ");
|
||
|
}
|
||
|
|
||
|
NCurses.WindowAddChar (window, ' ');
|
||
|
|
||
|
index ++;
|
||
|
}
|
||
|
|
||
|
NCurses.WindowRefresh (window);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Handle the user selection of the buttons
|
||
|
/// </summary>
|
||
|
/// <param name="window">Window to draw buttons on</param>
|
||
|
/// <param name="buttons">Buttons to display</param>
|
||
|
/// <returns>User choice of button, zero indexed</returns>
|
||
|
private static int InputHandler (nint window, MessageBoxButtons buttons) {
|
||
|
DrawButtons (window, buttons);
|
||
|
|
||
|
int index = 0;
|
||
|
int maxIndex = GetStringsFromButtonEnum (buttons).Count - 1;
|
||
|
|
||
|
int chr = 0;
|
||
|
while ((chr = NCurses.GetChar ()) != 10) {
|
||
|
if (chr == 260) { // Left
|
||
|
index ++;
|
||
|
if (index > maxIndex) index = 0;
|
||
|
DrawButtons (window, buttons, index);
|
||
|
} else if (chr == 261) { // Right
|
||
|
index --;
|
||
|
if (index < 0) index = maxIndex;
|
||
|
DrawButtons (window, buttons, index);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class ColorPairs {
|
||
|
public static int Ch_Topmenu_Label = 101;
|
||
|
public static int Ch_Topmenu_Label_Active = 102;
|
||
|
|
||
|
public static void InitColors () {
|
||
|
NCurses.InitPair (101, CursesColor.WHITE, CursesColor.BLUE);
|
||
|
NCurses.InitPair (102, CursesColor.BLACK, CursesColor.BLUE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class TopMenu {
|
||
|
private nint parentScreen;
|
||
|
private nint childWindow;
|
||
|
|
||
|
public List<MenuItem> MenuItems { get; set; } = new List<MenuItem> ();
|
||
|
|
||
|
public TopMenu (nint screen) {
|
||
|
parentScreen = screen;
|
||
|
|
||
|
NCurses.GetMaxYX (screen, out _, out int width);
|
||
|
childWindow = NCurses.NewWindow (1, width, 0, 0);
|
||
|
}
|
||
|
|
||
|
public void Render (int? activeItem = null) {
|
||
|
int index = 0;
|
||
|
uint
|
||
|
normalColorPair = 0,
|
||
|
activeColorPair = 0;
|
||
|
var hasColor = NCurses.HasColors ();
|
||
|
|
||
|
if (hasColor) {
|
||
|
normalColorPair = NCurses.ColorPair (ColorPairs.Ch_Topmenu_Label);
|
||
|
activeColorPair = NCurses.ColorPair (ColorPairs.Ch_Topmenu_Label_Active);
|
||
|
NCurses.WindowBackground (childWindow, normalColorPair);
|
||
|
NCurses.WindowRefresh (childWindow);
|
||
|
}
|
||
|
|
||
|
NCurses.MoveWindowAddString (childWindow, 0, 0, " ");
|
||
|
|
||
|
foreach (var item in MenuItems) {
|
||
|
var itemIsActive = activeItem is not null && index == (int) activeItem;
|
||
|
|
||
|
if (itemIsActive && hasColor) {
|
||
|
NCurses.WindowAttributeOn (childWindow, activeColorPair);
|
||
|
}
|
||
|
|
||
|
if (itemIsActive) {
|
||
|
NCurses.WindowAddString (childWindow, $"[{item.Label}] ");
|
||
|
} else {
|
||
|
NCurses.WindowAddString (childWindow, $" {item.Label} ");
|
||
|
}
|
||
|
|
||
|
if (itemIsActive && hasColor) {
|
||
|
NCurses.WindowAttributeOff (childWindow, activeColorPair);
|
||
|
}
|
||
|
|
||
|
index ++;
|
||
|
}
|
||
|
|
||
|
NCurses.WindowRefresh (childWindow);
|
||
|
}
|
||
|
|
||
|
public class MenuItem {
|
||
|
public required string Label { get; set; }
|
||
|
public string? HotKey { get; set; }
|
||
|
public MenuType Type { get; set; } = MenuType.Simple;
|
||
|
public List<MenuItem> SubMenuItems = new List<MenuItem> ();
|
||
|
|
||
|
public enum MenuType {
|
||
|
Simple,
|
||
|
TopMenu,
|
||
|
SubMenu,
|
||
|
Spacer
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|