Working program skeleton

main
resneptacle 4 weeks ago
parent be2c4fab03
commit e7e71ff820

@ -53,22 +53,6 @@ public class AsciiArt {
NCurses.WindowRefresh (window); NCurses.WindowRefresh (window);
} }
public static string logo38c3_80x24 { get {
return
@"
.;lllllllllc, .;c,:oddoc;. 'coddoc' ,clllllllll:.
kMMMMWKOdc;'xx cWMN' 'lkXMMO .dNMM0l' kMN: :MMMMMX0xl:':K
,;'..':ok0kc ;NMMN' .cNKl .NXx;. NMMW. ';,...;lxOOo.
l0NMMM0c. .:o0lldl, xMo,. dKKo ;kXWMMNd'
'WMMWo::oOXX0' :kO0kl;;lkK0k, OMMMMWK, 0MMMOc;lxKNKc
.,;;clodkXMc :MKo. .l0W. cMMMW0o. ;0WN, .',;:lodxKM0
cOkkxdollc::l: .XOddddxxxxxxkO cNMx ,0MMXo 'kOkxddolc:::l
.lxkkkkxoc,. ':odxkkkxoc' ,lc;lxo:. :dxkkkxdl;.
";
}}
public static string lorem_ipsum { get { public static string lorem_ipsum { get {
return return
@" @"

@ -1,189 +0,0 @@
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;
}
}

@ -1,3 +1,5 @@
#nullable disable
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse); // Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
using Newtonsoft.Json; using Newtonsoft.Json;

@ -1,114 +0,0 @@
using Fahrplan;
using Mindmagma.Curses;
using Newtonsoft.Json;
using SCI.CursesWrapper;
namespace ANSI_Fahrplan;
public class Playground {
public static void Run (string [] args) {
Console.WriteLine ("Program initializing Curses...");
// var jsonString = File.ReadAllText ("schedule.json");
// var schedule = JsonConvert.DeserializeObject<Fahrplan.Root> (jsonString);
// foreach (var day in schedule.schedule.conference.days) {
// Console.WriteLine ($"Day: {day.date}, {day.index}");
// foreach (var room in day.rooms) {
// Console.WriteLine ($" \\__ Room: {room.Key}");
// foreach (var ev in room.Value) {
// if (ev is null) continue;
// Console.WriteLine ($" \\__ Event: {ev.title}");
// }
// }
// }
var screen = NCurses.InitScreen ();
NCurses.NoEcho ();
NCurses.CBreak ();
NCurses.SetCursor (0);
var hasColors = NCurses.HasColors ();
if (hasColors) {
NCurses.StartColor ();
NCurses.InitPair (1, CursesColor.WHITE, CursesColor.BLUE);
//ColorPairs.InitColors ();
}
var win1 = NCurses.NewWindow (10, 20, 10, 10);
NCurses.Box (win1, (char) 0, (char) 0);
NCurses.WindowBackground (win1, ColorSchemes.Default ());
NCurses.MoveWindowAddString (win1, 1, 1, "Hello world!");
// Draw windows
NCurses.Refresh ();
NCurses.WindowRefresh (win1);
NCurses.GetChar ();
NCurses.EndWin ();
/*var window = CreateWindowCentered (screen, 40, 12);
NCurses.WindowBackground (window, NCurses.ColorPair (1));
NCurses.WindowAttributeOn (window, NCurses.ColorPair (1));*/
//NCurses.GetMaxYX (screen, out int screenHeight, out int screenWidth);
//NCurses.MoveAddString (screenHeight - 1, 0, "NCurses Example");
/*var menuItems = new List<TopMenu.MenuItem> () {
new TopMenu.MenuItem () { Label = "Test 1" },
new TopMenu.MenuItem () { Label = "Test 2" },
new TopMenu.MenuItem () { Label = "Test 3" },
};
var menu = new TopMenu (screen) { MenuItems = menuItems };
menu.Render ();*/
var msgBoxResponse = MessageBox.Show (screen, "Hello World, this is a message box text, yippee!! Let's make this text even longer, wooowiieeeee!", MessageBox.MessageBoxButtons.YesNo);
NCurses.AddString ($"Input was {msgBoxResponse}");
NCurses.Refresh ();
var inputTask = Task.Run (() => InputRoutine ());
while (inputTask.Status == TaskStatus.WaitingToRun);
while (inputTask.Status == TaskStatus.Running) {
NCurses.Refresh ();
//NCurses.WindowRefresh (window);
NCurses.Nap (1000 / 30);
}
//NCurses.AttributeOff (NCurses.ColorPair (1));
NCurses.EndWin ();
}
static void InputRoutine () {
int chr = 0;
while (chr != CursesKey.ESC) {
chr = NCurses.GetChar ();
/*if (chr == 265) {
menu.Render (0);
} else if (chr == 266) {
menu.Render (1);
} else if (chr == 267) {
menu.Render (2);
} else if (chr > 0) {
menu.Render ();
NCurses.MoveAddString (2, 10, $"You pressed the key {chr} ");
}*/
if (chr > 0) NCurses.MoveAddString (2, 10, $"You pressed the key {chr} ");
}
/*NCurses.WindowBorder (window, ' ', ' ', ' ',' ',' ',' ',' ',' ');
NCurses.WindowRefresh (window);
NCurses.DeleteWindow (window);*/
NCurses.MoveAddString (10, 10, "Input routine exited");
NCurses.Refresh ();
return;
}
}

@ -43,27 +43,20 @@ class Program {
// -- Intro screen -- // // -- Intro screen -- //
var introScreen = new IntroScreen (contentWindow); var introScreen = new IntroScreen (contentWindow);
introScreen.Draw ();
// Close intro screen on any keypress
introScreen.OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => {
((Window) sender).Destroy ();
};
introScreen.RegisterInputHandler (inputHandler); introScreen.RegisterInputHandler (inputHandler);
introScreen.SetWindowActive (); introScreen.SetWindowActive ();
// Wait until intro screen is closed // Wait until intro screen is closed
while (introScreen.WindowId > -1) Thread.Sleep (50); // TODO; Unjank this while (introScreen.WindowId > -1) Thread.Sleep (50); // TODO; Unjank this
introScreen = null;
// -- Create footer bar -- //
var footerWindow = new FooterWindow (screen);
footerWindow.DrawFooter ("Fahrplan", "LLLeft", "RRRRRight");
// -- Create menu bar -- // // -- Create menu bar -- //
var topMenu = new TopMenu (screen, inputHandler, CreateMenuItems (contentWindow)); var topMenu = new TopMenu (screen, inputHandler, CreateMenuItems (contentWindow));
topMenu.ActivateItem (topMenu.MenuItems [1]);
// Testing
var testwin = contentWindow.CreateInnerWindow ();
testwin.BackgroundColorId = ColorSchemes.TextInputField ();
NCurses.WindowAddString (testwin, "Hello World.");
contentWindow.Draw ();
// Wait until the input handler routine stops // Wait until the input handler routine stops
while (inputHandler.IsListening ()) Thread.Sleep (50); while (inputHandler.IsListening ()) Thread.Sleep (50);

@ -1,4 +1,3 @@
using Mindmagma.Curses;
using SCI.CursesWrapper; using SCI.CursesWrapper;
namespace ANSI_Fahrplan.Screens; namespace ANSI_Fahrplan.Screens;
@ -28,5 +27,10 @@ public class IntroScreen : Window {
"38C3 Fahrplan in your terminal!"; "38C3 Fahrplan in your terminal!";
AsciiArt.ShowCentered (WindowId, asciiArtPlusText); AsciiArt.ShowCentered (WindowId, asciiArtPlusText);
// Close intro screen on any keypress
OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => {
((Window) sender).Destroy ();
};
} }
} }

@ -1,27 +0,0 @@
using Mindmagma.Curses;
namespace ANSI_Fahrplan.Screens;
public abstract class Screen {
private nint _rootWindow;
public nint RootWindow { get { return _rootWindow; }}
public Screen (nint rootWindow) {
_rootWindow = rootWindow;
PrepareWindow ();
}
private void PrepareWindow () {
NCurses.NoEcho ();
NCurses.CBreak ();
NCurses.SetCursor (0);
//NCurses.ClearWindow (RootWindow);
NCurses.WindowMove (RootWindow, 0, 0);
}
public abstract void Show ();
public abstract void Hide ();
}

@ -1,13 +0,0 @@
- [x] Create JSON parser for Fahrplan
- [] Add networking functions to pull up-to-date Fahrplan from web
- [] User interface
- [] Add landing page
- [] Add schedule overview by
- [] Day (List view, go left and right to change days)
- [] Room ()
- [] Category
- [] Speakers
- [] Today/Currently running
- [] Timeline?
- [] Add search interface
- [] Add about page

@ -0,0 +1,64 @@
using Mindmagma.Curses;
namespace SCI.CursesWrapper.UiElements;
public class FooterWindow : Window {
/// <summary>
/// Class constructor
/// </summary>
/// <param name="screen">Root NCurses screen ID</param>
/// <param name="defaultTitle">Initial title to show</param>
public FooterWindow (nint screen, string defaultTitle = "") :
base (0, CursesWrapper.GetHeight (screen) - 1, CursesWrapper.GetWidth (screen), 1)
{
var colorSchemeNormal = ColorSchemes.TopMenuNormal ();
BackgroundColorId = colorSchemeNormal;
NCurses.WindowAttributeOn (WindowId, colorSchemeNormal);
DrawFooter (defaultTitle);
}
/// <summary>
/// Draw the footer window and insert the specified text
/// </summary>
/// <param name="title"></param>
/// <param name="left"></param>
/// <param name="right"></param>
public void DrawFooter (string title, string left = "", string right = "") {
Clear ();
// TODO: Describe the last-character-in-window bug(fix) here
var pad =
(WindowSize.Width / 2) -
(title.Length / 2)
- 1;
var finalString =
" " + left.PadRight (pad) +
title +
(string.IsNullOrEmpty (right)?
"" :
right.PadLeft (pad)
);
// String too long, leave out left and right text
if (finalString.Length > WindowSize.Width - 1) {
finalString = " ".PadLeft (pad) + title;
}
// String still too long, leave out the padding and left align title
if (finalString.Length > WindowSize.Width - 1) {
finalString = " " + title;
}
// String still too long, cut to size
if (finalString.Length > WindowSize.Width - 1) {
finalString = title.Substring (0, WindowSize.Width - 1);
}
try {
NCurses.MoveWindowAddString (this, 0, 0, finalString);
Draw ();
} catch (Exception) {};
}
}

@ -1,97 +0,0 @@
using Mindmagma.Curses;
namespace SCI.CursesWrapper.UiElements;
public class InputBox {
public delegate void InputCompleted (string input);
public void RequestInput (nint screen, InputHandler inputHandler, InputCompleted callback, string question) {
const int boxPaddingTop = 1;
const int boxPaddingSides = 2;
const int boxPaddingBottom = 2;
const int boxMarginTopBottom = 1;
const int boxMarginSides = 2;
NCurses.GetMaxYX (screen, out int height, out int width);
// Contents inside the question box may not be larger than these
int maxInnerWidth = width - (boxPaddingSides * 2) - (boxMarginSides * 2);
int maxInnerHeight = height - boxPaddingTop - boxPaddingBottom - boxMarginTopBottom;
using var strReader = new StringReader (question);
string? inStr;
int innerWidth = 1;
int innerHeight = 1;
while ((inStr = strReader.ReadLine ()) is not null) {
if (inStr.Length > innerWidth) innerWidth = inStr.Length;
if (innerWidth > maxInnerWidth) {
innerWidth = maxInnerWidth;
innerHeight += (int) Math.Ceiling (inStr.Length / (double) maxInnerWidth);
} else {
innerHeight ++;
}
}
int boxWidth = innerWidth + (boxPaddingSides * 2);
int boxHeight = innerHeight + boxPaddingTop + boxPaddingBottom;
int boxAnchorX = (width / 2) - (boxWidth / 2);
int boxAnchorY = (height / 2) - (boxHeight / 2);
var boxWin = NCurses.SubWindow (screen, boxHeight, boxWidth, boxAnchorY, boxAnchorX);
NCurses.Box (boxWin, (char) 0, (char) 0);
NCurses.Refresh ();
NCurses.WindowRefresh (boxWin);
int questionTextWinAnchorX = boxAnchorX + boxPaddingSides;
int questionTextWinAnchorY = boxAnchorY + boxPaddingTop;
var questionTextWin = NCurses.SubWindow (boxWin, innerHeight, innerWidth, questionTextWinAnchorY, questionTextWinAnchorX);
NCurses.WindowAddString (questionTextWin, question.Trim ());
NCurses.WindowRefresh (questionTextWin);
int inputFieldWinX = questionTextWinAnchorX;
int inputFieldWinY = boxAnchorY + boxHeight - boxMarginTopBottom - 1;
var inputFieldWin = NCurses.SubWindow (boxWin, 1, innerWidth, inputFieldWinY, inputFieldWinX);
NCurses.WindowBackground (inputFieldWin, ColorSchemes.TextInputField ());
NCurses.WindowAttributeSet (inputFieldWin, ColorSchemes.TextInputField ());
NCurses.WindowRefresh (inputFieldWin);
NCurses.MoveWindow (inputFieldWin, inputFieldWinY, inputFieldWinX);
NCurses.SetCursor (1);
NCurses.Echo ();
var userInput = "";
InputHandler.KeypressEventHandler handlerFunction = (object sender, NCursesKeyPressEventArgs e) => {
if (e.KeyCode == '\n' || e.KeyCode == CursesKey.ENTER) {
callback (userInput);
}
if (e.KeyCode == '\n' || e.KeyCode == CursesKey.ENTER || e.KeyCode == CursesKey.ESC) {
NCurses.SetCursor (0);
NCurses.NoEcho ();
//inputHandler.DisableRawEventHandler (screen);
NCurses.MoveWindow (screen, 0, 0);
NCurses.DeleteWindow (inputFieldWin);
NCurses.DeleteWindow (questionTextWin);
NCurses.DeleteWindow (boxWin);
NCurses.Refresh ();
} else if (e.KeyCode == CursesKey.BACKSPACE) {
userInput = userInput.Substring (0, userInput.Length - 2);
} else if (char.IsAscii ((char) e.KeyCode)) {
userInput += (char) e.KeyCode;
}
NCurses.MoveWindow (inputFieldWin, inputFieldWinY, inputFieldWinX);
};
//inputHandler.EnableRawEventHandler (handlerFunction, inputFieldWin);
}
}

@ -55,18 +55,27 @@ public class MenuItem {
if (OnItemActivated is null) return; if (OnItemActivated is null) return;
if (ParentTopMenuWindow is null) return; if (ParentTopMenuWindow is null) return;
var eventArgs = new MenuItemActivatedEventArgs (ParentTopMenuWindow, this);
if (Key.Length > 1 && Key.StartsWith ("F")) { // Handle F keys if (Key.Length > 1 && Key.StartsWith ("F")) { // Handle F keys
if (e.KeyCode == CursesKey.KEY_F (int.Parse (Key.Substring (1)))) { if (e.KeyCode == CursesKey.KEY_F (int.Parse (Key.Substring (1)))) {
OnItemActivated (this, eventArgs); ActivateItem ();
} }
} else if (Key.Length == 1) { // Handle letters and numbers } else if (Key.Length == 1) { // Handle letters and numbers
if (e.KeyCode == Key [0]) { if (e.KeyCode == Key [0]) {
OnItemActivated (this, eventArgs); ActivateItem ();
} }
} else throw new NotImplementedException ("Currently only F-keys and letters work for Top Menu actions"); } else throw new NotImplementedException ("Currently only F-keys and letters work for Top Menu actions");
} }
/// <summary>
/// Call event handler assigned to this item being activated
/// </summary>
public void ActivateItem () {
if (OnItemActivated is null) return;
if (ParentTopMenuWindow is null) return;
var eventArgs = new MenuItemActivatedEventArgs (ParentTopMenuWindow, this);
OnItemActivated (this, eventArgs);
}
} }
public class MenuItemActivatedEventArgs : EventArgs { public class MenuItemActivatedEventArgs : EventArgs {

@ -35,6 +35,14 @@ public class TopMenu : Window {
DrawMenu (); DrawMenu ();
} }
/// <summary>
/// Finalizer, clean up event handlers
/// </summary>
~TopMenu () {
UnregisterItemEventHandlers ();
Destroy ();
}
/// <summary> /// <summary>
/// Register key event handlers for each menu item /// Register key event handlers for each menu item
/// </summary> /// </summary>
@ -71,6 +79,22 @@ public class TopMenu : Window {
DrawMenu (); DrawMenu ();
} }
/// <summary>
///
/// </summary>
/// <param name="item"></param>
public void ActivateItem (MenuItem item) {
foreach (var myItem in MenuItems) {
if (myItem == item) {
ItemActivatedHandler (myItem, EventArgs.Empty);
myItem.ActivateItem ();
return;
}
}
throw new Exception ("This item does not exist");
}
/// <summary> /// <summary>
/// Draw the top menu with currently configured menu items /// Draw the top menu with currently configured menu items
/// </summary> /// </summary>

@ -1,12 +0,0 @@
namespace ANSI_Fahrplan.UiElements;
public abstract class UiElement {
private nint _screen;
public nint Screen { get { return _screen; }}
protected nint contentWindow { get; set; }
public UiElement (nint screen) {
_screen = screen;
}
}
Loading…
Cancel
Save