i'm bad at git, large commit with lots of changes

main
resneptacle 4 weeks ago
parent 04677ab97a
commit a6377d1a57

@ -57,14 +57,14 @@ public class AsciiArt {
return
@"
.;lllllllllc, .;c,:oddoc;. 'coddoc' ,clllllllll:.
.;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
,;'..':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;.
.lxkkkkxoc,. ':odxkkkxoc' ,lc;lxo:. :dxkkkxdl;.
";
}}

@ -17,22 +17,22 @@ public class Helper {
/// <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);
NCurses.GetMaxYX (screen, out int screenHeight, out int screenWidth);
int originY = (screenHeight / 2) - (height / 2);
int originX = (screenWidth / 2) - (width / 2);
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 + 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");
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);
nint window = NCurses.NewWindow (height, width, originY, originX);
if (borders) NCurses.Box (window, (char) 0, (char) 0);
return window;
}
return window;
}
/// <summary>
/// Prints text horizontally centered to window (on a specific line);
@ -70,13 +70,13 @@ public class MessageBox {
// 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);
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 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;
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;
}
@ -187,81 +187,3 @@ public class MessageBox {
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
}
}
}

@ -3,39 +3,54 @@ using Mindmagma.Curses;
namespace SCI.CursesWrapper;
public class CursesWrapper {
/// <summary>
/// Initializes NCurses and creates a screen
/// </summary>
public static nint InitNCurses () {
var screen = NCurses.InitScreen ();
/// <summary>
/// Initializes NCurses and creates a screen
/// </summary>
public static nint InitNCurses () {
var screen = NCurses.InitScreen ();
NCurses.SetCursor (0);
NCurses.UseDefaultColors ();
NCurses.NoEcho ();
NCurses.Raw ();
NCurses.Keypad (screen, true);
NCurses.NoDelay (screen, false);
if (NCurses.HasColors ()) {
if (NCurses.HasColors ()) {
NCurses.StartColor ();
ColorSchemes.InitAll ();
}
return screen;
}
return screen;
}
/// <summary>
/// Get the total width of a window
/// </summary>
/// <param name="window">Window to query</param>
/// <returns>Width of window in columns</returns>
public static int GetWidth (nint window) {
NCurses.GetMaxYX (window, out int _, out int width);
return width;
}
/// <summary>
/// Get the total width of a window/screen
/// </summary>
/// <param name="window">Window to query</param>
/// <returns>Width of window in columns</returns>
public static int GetWidth (nint window) {
NCurses.GetMaxYX (window, out int _, out int width);
return width;
}
/// <summary>
/// Get the total height of a window
/// </summary>
/// <param name="window">Window to query</param>
/// <returns>Height of window in rows</returns>
public static int GetHeight (nint window) {
NCurses.GetMaxYX (window, out int height, out int _);
return height;
}
/// <summary>
/// Get the total height of a window
/// </summary>
/// <param name="window">Window to query</param>
/// <returns>Height of window in rows</returns>
public static int GetHeight (nint window) {
NCurses.GetMaxYX (window, out int height, out int _);
return height;
}
/// <summary>
/// Compares a key code to a character to test for a held
/// Control key during the key press event
/// </summary>
/// <param name="keyCode"></param>
/// <param name="testChar"></param>
/// <returns></returns>
public static bool KeyPressIsCtrl (int keyCode, int testChar) {
return keyCode == (testChar & 0x1f);
}
}

@ -0,0 +1,12 @@
namespace SCI.CursesWrapper;
public class InnerWindow : Window {
public InnerWindow (nint rootScreen, InputHandler inputHandler) :
base (
0, 1,
CursesWrapper.GetWidth (rootScreen),
CursesWrapper.GetHeight (rootScreen) - 2,
inputHandler
)
{ }
}

@ -1,3 +1,4 @@
using System.Diagnostics;
using Mindmagma.Curses;
namespace SCI.CursesWrapper;
@ -33,11 +34,6 @@ public class InputHandler {
}
}
/// <summary>
/// Root screen to fall back to when no window is active
/// </summary>
private nint rootScreen;
/// <summary>
/// Task running the endless input handler routine
/// </summary>
@ -48,14 +44,6 @@ public class InputHandler {
/// </summary>
private CancellationTokenSource? listeningRoutineCts;
/// <summary>
/// Class constructor
/// <paramref name="screen">Parent screen this InputHandler is attached to</paramref>
/// </summary>
public InputHandler (nint screen) {
rootScreen = screen;
}
/// <summary>
/// Start listening to key presses and call registered callbacks
/// </summary>
@ -96,59 +84,59 @@ public class InputHandler {
);
}
/// <summary>
/// Prepares the window for proper key press reading
/// </summary>
private void WindowSetup () {
if (ActiveWindow is not null) {
NCurses.Keypad (ActiveWindow.WindowId, true);
NCurses.NoDelay (ActiveWindow.WindowId, false);
} else {
NCurses.Keypad (rootScreen, true);
NCurses.NoDelay (rootScreen, false);
}
}
/// <summary>
/// Undoes any changes the setup routine configured for the active window
/// </summary>
private void WindowTeardown () {
if (ActiveWindow is not null) {
NCurses.Keypad (ActiveWindow.WindowId, false);
NCurses.NoDelay (ActiveWindow.WindowId, true);
} else {
NCurses.Keypad (rootScreen, false);
NCurses.NoDelay (rootScreen, true);
}
}
/// <summary>
/// Actual keypress handler being called as Task by StartListening ()
/// </summary>
private void _ListeningRoutine () {
if (listeningRoutineCts is null) return;
WindowSetup ();
int keyCode = -1;
while (!listeningRoutineCts.Token.IsCancellationRequested) {
if (OnKeyPress is null) {
Console.Title = "No handlers";
} else {
Console.Title = $"Got {OnKeyPress.GetInvocationList ().Length} handlers";
}
int keyCode = -1;
int keyCode2 = -1;
if (ActiveWindow is not null) {
keyCode = NCurses.WindowGetChar (ActiveWindow.WindowId);
// If this block looks yanky; it is.
// This code checks if ESC was pressed and waits for another
// key press for another 10 milliseconds. This is to catch
// key combinations with the Alt key, as it is sent as
// [Alt] + [<key>]
if (keyCode == CursesKey.ESC) {
NCurses.WindowTimeOut (ActiveWindow.WindowId, 10);
try {
keyCode2 = NCurses.WindowGetChar (ActiveWindow.WindowId);
} catch (Exception) {
keyCode2 = -1;
}
NCurses.WindowTimeOut (ActiveWindow.WindowId, -1);
}
} else {
keyCode = NCurses.GetChar ();
// Janky solution, see above
if (keyCode == CursesKey.ESC) {
NCurses.TimeOut (10);
try {
keyCode2 = NCurses.GetChar ();
} catch (Exception) {
keyCode2 = -1;
}
NCurses.TimeOut (-1);
}
}
// Do nothing until the key code is larger than -1
if (keyCode < 0) continue;
// Do nothing until either key code is larger than -1
if (keyCode < 0 && keyCode2 < 0) continue;
// Test if [Alt]+[key] combination was pressed.
// This is due to [Alt]+[key] being sent as [ESC], [key]
bool isAltCombo = keyCode == CursesKey.ESC && keyCode2 != -1;
if (isAltCombo) {
keyCode = keyCode2;
}
var eventArgs = new NCursesKeyPressEventArgs (keyCode, ActiveWindow);
var eventArgs = new NCursesKeyPressEventArgs (keyCode, ActiveWindow, isAltCombo);
// Handle any registered privileged key handlers
if (OnKeyPressPrivileged is not null) {
@ -183,8 +171,12 @@ public class NCursesKeyPressEventArgs : EventArgs {
public bool CancelNextEvent { get; set; } = false;
public NCursesKeyPressEventArgs (int keyCode, Window? sourceWindow) {
private bool _isAltCombination = false;
public bool IsAltCombination { get { return _isAltCombination; } }
public NCursesKeyPressEventArgs (int keyCode, Window? sourceWindow, bool isAltCombination) {
_keyCode = keyCode;
_sourceWindow = sourceWindow;
_isAltCombination = isAltCombination;
}
}

@ -5,261 +5,351 @@ using Mindmagma.Curses;
namespace SCI.CursesWrapper;
public class Window {
/// <summary>
/// Gets or sets the window position on the screen
/// </summary>
private Point _position;
public Point Position {
get {
return _position;
}
set {
if (ParentWindow is not null) {
NCurses.WindowMove (
WindowId,
ParentWindow.Position.Y + value.Y,
ParentWindow.Position.X + value.X
);
} else {
NCurses.WindowMove (
WindowId,
value.Y,
value.X
);
}
_position = value;
}
}
/// <summary>
/// Gets or sets the width and height of the window
/// </summary>
private Size _windowSize;
public Size WindowSize {
get {
return _windowSize;
}
set {
_windowSize = value;
}
}
/// <summary>
/// Gets or sets the window background color pair
/// </summary>
private uint _backgroundColorId;
public uint BackgroundColorId {
get {
return _backgroundColorId;
}
set {
NCurses.WindowBackground (WindowId, value);
Redraw ();
_backgroundColorId = value;
}
}
/// <summary>
/// Sets the parent window
/// </summary>
private Window? _parentWindow;
public Window? ParentWindow { get { return _parentWindow; }}
/// <summary>
/// Holds a list of children windows this window posesses
/// </summary>
private List<Window> _childWindows = new List<Window> ();
public ReadOnlyCollection<Window> ChildWindows { get { return _childWindows.AsReadOnly (); }}
/// <summary>
/// Holds the pointer for this window
/// </summary>
private nint _windowId;
public nint WindowId { get { return _windowId; }}
/// <summary>
/// Input handler assigned to this window
/// </summary>
private InputHandler? inputHandler;
/// <summary>
/// Event handler called when this window is active and a key is pressed
/// </summary>
public event InputHandler.KeypressEventHandler? OnKeyPress;
/// <summary>
/// Create new window by specifying X/Y and Width/Height geometry
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="parentWindow"></param>
public Window (int x, int y, int width, int height, Window? parentWindow = null) {
if (parentWindow is not null) {
_windowId = NCurses.DeriveWindow (
parentWindow.WindowId,
height, width,
y, x
);
} else {
_windowId = NCurses.NewWindow (
height, width,
y, x
);
}
Redraw ();
}
/// <summary>
/// Create new window by specifying geometry through Point and Size objects
/// </summary>
/// <param name="position"></param>
/// <param name="windowSize"></param>
/// <param name="parentWindow"></param>
public Window (Point position, Size windowSize, Window? parentWindow = null) {
if (parentWindow is not null) {
_windowId = NCurses.SubWindow (
parentWindow.WindowId,
windowSize.Height, windowSize.Width,
position.Y, position.X
);
} else {
_windowId = NCurses.NewWindow (
windowSize.Height, windowSize.Width,
position.Y, position.X
);
}
Redraw ();
}
/// <summary>
/// Adds a child window to this window
/// </summary>
/// <param name="child"></param>
public void AddChildWindow (Window child) {
if (_childWindows.Contains (child)) return;
_childWindows.Add (child);
}
/// <summary>
/// Removes a child window from this window
/// </summary>
/// <param name="child"></param>
public void RemoveChildWindow (Window child) {
if (!_childWindows.Contains (child)) return;
_childWindows.Remove (child);
}
/// <summary>
/// Discards all optimization options about drawn parts of this window.
/// Call before drawing a sub window
/// </summary>
public void TouchWin () {
NCurses.TouchWindow (WindowId);
}
/// <summary>
/// Redraws this window
/// </summary>
public void Redraw () {
NCurses.Refresh (); // TODO: Necessary?
if (ChildWindows.Count > 0) {
foreach (var window in ChildWindows) {
window.Redraw ();
}
}
if (ParentWindow is not null) ParentWindow.TouchWin ();
NCurses.WindowRefresh (WindowId);
}
/// <summary>
/// Destroys this window and all children windows
/// </summary>
public void Destroy () {
if (ChildWindows.Count > 0) {
foreach (var window in _childWindows) {
window.Destroy ();
}
}
if (inputHandler is not null) inputHandler.ActiveWindow = null;
if (ParentWindow is not null) ParentWindow.RemoveChildWindow (this);
UnregisterInputHandler ();
SetBorder (false);
NCurses.ClearWindow (WindowId);
Console.Title = "About to destroy";
NCurses.DeleteWindow (WindowId);
Console.Title = "Destroyed";
//TODO: Program hangs on DeleteWindow
}
/// <summary>
/// Register an input handler for this window to attach to OnKeyPress events
/// </summary>
/// <param name="inputHandler">InputHandler to register</param>
public void RegisterInputHandler (InputHandler targetInputHandler) {
if (inputHandler is not null) throw new Exception (
"Another input handler is already registered"
);
inputHandler = targetInputHandler;
inputHandler.OnKeyPress += KeyPressHandler;
}
/// <summary>
/// Detach from all OnKeyPress events and unset input handler
/// </summary>
public void UnregisterInputHandler () {
if (inputHandler is null) return;
inputHandler.OnKeyPress -= KeyPressHandler;
}
/// <summary>
/// Handle key press events from the input handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void KeyPressHandler (object sender, NCursesKeyPressEventArgs e) {
if (e.SourceWindow != this) return;
if (OnKeyPress is not null) {
OnKeyPress (sender, e);
}
}
/// <summary>
/// Tells the input handler this window is active
/// </summary>
public void SetWindowActive () {
if (inputHandler is null) return;
inputHandler.ActiveWindow = this;
}
/// <summary>
/// Enables or disables a border around this window
/// </summary>
/// <param name="enabled">Sets the status of the border</param>
/// <param name="horizontalChar">If specified, uses this character as the top and bottom border</param>
/// <param name="verticalChar">If specified, uses this character as the left and right border</param>
public void SetBorder (bool enabled, char? horizontalChar = null, char? verticalChar = null) {
if (horizontalChar is null) horizontalChar = (char) 0;
if (verticalChar is null) verticalChar = (char) 0;
if (enabled) {
NCurses.Box (WindowId, (char) horizontalChar, (char) verticalChar);
} else {
NCurses.Box (WindowId, ' ', ' ');
}
}
public const int InnerPadding = 1;
/// <summary>
/// Gets or sets the window position on the screen
/// </summary>
private Point _position;
public Point Position {
get {
return _position;
}
set {
if (ParentWindow is not null) {
int addedPadding = ParentWindow.BorderEnabled?
InnerPadding + 1 : 0;
NCurses.WindowMove (
WindowId,
ParentWindow.Position.Y + value.Y + addedPadding,
ParentWindow.Position.X + value.X + addedPadding
);
} else {
NCurses.WindowMove (
WindowId,
value.Y,
value.X
);
}
_position = value;
}
}
/// <summary>
/// Gets or sets the width and height of the window
/// </summary>
private Size _windowSize;
public Size WindowSize {
get {
return _windowSize;
}
set {
_windowSize = value;
}
}
/// <summary>
/// Gets or sets the window background color pair
/// </summary>
private uint _backgroundColorId;
public uint BackgroundColorId {
get {
return _backgroundColorId;
}
set {
NCurses.WindowBackground (WindowId, value);
Draw ();
_backgroundColorId = value;
}
}
private bool _borderEnabled;
public bool BorderEnabled {
get {
return _borderEnabled;
}
set {
SetBorder (value);
Draw ();
_borderEnabled = value;
}
}
/// <summary>
/// Sets the parent window
/// </summary>
private Window? _parentWindow;
public Window? ParentWindow { get { return _parentWindow; }}
/// <summary>
/// Holds a list of children windows this window posesses
/// </summary>
private List<Window> _childWindows = new List<Window> ();
public ReadOnlyCollection<Window> ChildWindows { get { return _childWindows.AsReadOnly (); }}
/// <summary>
/// Holds the pointer for this window
/// </summary>
private nint _windowId;
public nint WindowId { get { return _windowId; }}
/// <summary>
/// Input handler assigned to this window
/// </summary>
protected InputHandler? _targetInputHandler;
public InputHandler? TargetInputHandler { get { return _targetInputHandler; }}
/// <summary>
/// Event handler called when this window is active and a key is pressed
/// </summary>
public event InputHandler.KeypressEventHandler? OnKeyPress;
/// <summary>
/// Create new window by specifying X/Y and Width/Height geometry
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="parentWindow"></param>
public Window (int x, int y, int width, int height, Window? parentWindow = null) {
_Initialize (new Point (x, y), new Size (width, height), parentWindow);
}
/// <summary>
/// Create new window by specifying geometry through Point and Size objects
/// </summary>
/// <param name="position"></param>
/// <param name="windowSize"></param>
/// <param name="parentWindow"></param>
public Window (Point position, Size windowSize, Window? parentWindow = null) {
_Initialize (position, windowSize, parentWindow);
}
/// <summary>
/// Create new window by specifying X/Y and Width/Height geometry
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="targetInputHandler"></param>
/// <param name="parentWindow"></param>
public Window (int x, int y, int width, int height, InputHandler targetInputHandler, Window? parentWindow = null) {
_Initialize (new Point (x, y), new Size (width, height), parentWindow);
RegisterInputHandler (targetInputHandler);
SetWindowActive ();
}
/// <summary>
/// Create new window by specifying geometry through Point and Size objects
/// </summary>
/// <param name="position"></param>
/// <param name="windowSize"></param>
/// <param name="targetInputHandler"></param>
/// <param name="parentWindow"></param>
public Window (Point position, Size windowSize, InputHandler targetInputHandler, Window? parentWindow = null) {
_Initialize (position, windowSize, parentWindow);
RegisterInputHandler (targetInputHandler);
SetWindowActive ();
}
/// <summary>
/// Actual initialization function for this class
/// </summary>
/// <param name="position"></param>
/// <param name="windowSize"></param>
/// <param name="parentWindow"></param>
private void _Initialize (Point position, Size windowSize, Window? parentWindow = null) {
if (parentWindow is not null) {
int addedPadding = parentWindow.BorderEnabled?
InnerPadding + 1 : 0;
_windowId = NCurses.DeriveWindow (
parentWindow.WindowId,
windowSize.Height, windowSize.Width,
position.Y + addedPadding, position.X + addedPadding
);
parentWindow.AddChildWindow (this);
} else {
_windowId = NCurses.NewWindow (
windowSize.Height, windowSize.Width,
position.Y, position.X
);
}
_position = position;
_windowSize = windowSize;
_parentWindow = parentWindow;
NCurses.Keypad (WindowId, true);
Draw ();
}
/// <summary>
/// Destroys the window and its sub-windows when the Window object is destroyed
/// </summary>
~Window () {
Destroy ();
}
/// <summary>
/// Adds a child window to this window
/// </summary>
/// <param name="child"></param>
public void AddChildWindow (Window child) {
if (_childWindows.Contains (child)) return;
_childWindows.Add (child);
}
/// <summary>
/// Removes a child window from this window
/// </summary>
/// <param name="child"></param>
public void RemoveChildWindow (Window child) {
_childWindows.Remove (child);
}
/// <summary>
/// Discards all optimization options about drawn parts of this window.
/// Call before drawing a sub window
/// </summary>
public void TouchWin () {
NCurses.TouchWindow (WindowId);
}
/// <summary>
/// Draws this window and all sub windows
/// </summary>
public void Draw () {
NCurses.Refresh (); // TODO: Necessary?
foreach (var window in ChildWindows) {
window.Draw ();
}
if (ParentWindow is not null) ParentWindow.TouchWin ();
NCurses.WindowRefresh (WindowId);
}
/// <summary>
/// Destroys this window and all children windows
/// </summary>
public void Destroy () {
// Catch double destroy calls
if (WindowId == -1) throw new Exception ("Destroy called twice on object");
foreach (var window in _childWindows.ToList ()) {
window.Destroy ();
}
UnregisterInputHandler ();
// Prepare for screen clear
NCurses.WindowBackground (WindowId, NCurses.ColorPair (-1));
SetBorder (false);
// Clear window and redraw
NCurses.ClearWindow (WindowId);
Draw ();
NCurses.DeleteWindow (WindowId);
_windowId = -1;
// Ensures sure the parent is updated too
if (ParentWindow is not null) {
ParentWindow.RemoveChildWindow (this);
ParentWindow.Draw ();
}
}
/// <summary>
/// Register an input handler for this window to attach to OnKeyPress events
/// </summary>
/// <param name="targetInputHandler">InputHandler to register</param>
public void RegisterInputHandler (InputHandler inputHandler) {
if (TargetInputHandler is not null) throw new Exception (
"Another input handler is already registered"
);
_targetInputHandler = inputHandler;
TargetInputHandler!.OnKeyPress += KeyPressHandler;
}
/// <summary>
/// Detach from all OnKeyPress events and unset input handler
/// </summary>
public void UnregisterInputHandler () {
if (TargetInputHandler is null) return;
if (TargetInputHandler.ActiveWindow == this) TargetInputHandler.ActiveWindow = null;
TargetInputHandler.OnKeyPress -= KeyPressHandler;
}
/// <summary>
/// Handle key press events from the input handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void KeyPressHandler (object sender, NCursesKeyPressEventArgs e) {
if (e.SourceWindow != this) return;
if (OnKeyPress is not null) {
OnKeyPress (this, e);
}
}
/// <summary>
/// Tells the input handler this window is active
/// </summary>
public void SetWindowActive () {
if (TargetInputHandler is null) return;
TargetInputHandler.ActiveWindow = this;
Draw ();
}
/// <summary>
/// Enables or disables a border around this window
/// </summary>
/// <param name="enabled">Sets the status of the border</param>
/// <param name="horizontalChar">If specified, uses this character as the top and bottom border</param>
/// <param name="verticalChar">If specified, uses this character as the left and right border</param>
public void SetBorder (bool enabled, char? horizontalChar = null, char? verticalChar = null) {
if (horizontalChar is null) horizontalChar = (char) 0;
if (verticalChar is null) verticalChar = (char) 0;
if (enabled) {
NCurses.Box (WindowId, (char) horizontalChar, (char) verticalChar);
} else {
NCurses.Box (WindowId, ' ', ' ');
}
}
/// <summary>
/// Calcaulates the usable inner width of this window
/// </summary>
/// <returns>Usable inner width of window in columns</returns>
public int GetUsableWidth () {
if (BorderEnabled) {
return WindowSize.Width - 1 - InnerPadding;
} else {
return WindowSize.Width;
}
}
/// <summary>
/// Calcaulates the usable inner height of this window
/// </summary>
/// <returns>Usable inner height of window in rows</returns>
public int GetUsableHeight () {
if (BorderEnabled) {
return WindowSize.Height - 1 - InnerPadding;
} else {
return WindowSize.Height;
}
}
}

@ -4,94 +4,94 @@ using Newtonsoft.Json;
namespace Fahrplan;
public class Root {
[JsonProperty("$schema")]
public string schema { get; set; }
public Generator generator { get; set; }
public Schedule schedule { get; set; }
[JsonProperty("$schema")]
public string schema { get; set; }
public Generator generator { get; set; }
public Schedule schedule { get; set; }
}
public class Colors {
public string primary { get; set; }
public string primary { get; set; }
}
public class Conference {
public string acronym { get; set; }
public string title { get; set; }
public string start { get; set; }
public string end { get; set; }
public int daysCount { get; set; }
public string timeslot_duration { get; set; }
public string time_zone_name { get; set; }
public Colors colors { get; set; }
public List<Room> rooms { get; set; }
public List<Track> tracks { get; set; }
public List<Day> days { get; set; }
public string acronym { get; set; }
public string title { get; set; }
public string start { get; set; }
public string end { get; set; }
public int daysCount { get; set; }
public string timeslot_duration { get; set; }
public string time_zone_name { get; set; }
public Colors colors { get; set; }
public List<Room> rooms { get; set; }
public List<Track> tracks { get; set; }
public List<Day> days { get; set; }
}
public class Day {
public int index { get; set; }
public string date { get; set; }
public DateTime day_start { get; set; }
public DateTime day_end { get; set; }
public Dictionary<string, List<RoomEvent>> rooms { get; set; }
public int index { get; set; }
public string date { get; set; }
public DateTime day_start { get; set; }
public DateTime day_end { get; set; }
public Dictionary<string, List<RoomEvent>> rooms { get; set; }
}
public class Generator {
public string name { get; set; }
public string version { get; set; }
public string name { get; set; }
public string version { get; set; }
}
public class Person {
public string guid { get; set; }
public int id { get; set; }
public string code { get; set; }
public string public_name { get; set; }
public string avatar { get; set; }
public string biography { get; set; }
public List<object> answers { get; set; }
public string guid { get; set; }
public int id { get; set; }
public string code { get; set; }
public string public_name { get; set; }
public string avatar { get; set; }
public string biography { get; set; }
public List<object> answers { get; set; }
}
public class Room {
public string name { get; set; }
public string guid { get; set; }
public object description { get; set; }
public int capacity { get; set; }
public string name { get; set; }
public string guid { get; set; }
public object description { get; set; }
public int capacity { get; set; }
}
public class Track {
public string name { get; set; }
public string color { get; set; }
public string name { get; set; }
public string color { get; set; }
}
public class RoomEvent {
public string url { get; set; }
public int id { get; set; }
public string guid { get; set; }
public DateTime date { get; set; }
public string start { get; set; }
public string logo { get; set; }
public string duration { get; set; }
public string room { get; set; }
public string slug { get; set; }
public string title { get; set; }
public string subtitle { get; set; }
public string track { get; set; }
public string type { get; set; }
public string language { get; set; }
public string @abstract { get; set; }
public string description { get; set; }
public string recording_license { get; set; }
public bool do_not_record { get; set; }
public List<Person> persons { get; set; }
public List<object> links { get; set; }
public List<object> attachments { get; set; }
public List<object> answers { get; set; }
public string url { get; set; }
public int id { get; set; }
public string guid { get; set; }
public DateTime date { get; set; }
public string start { get; set; }
public string logo { get; set; }
public string duration { get; set; }
public string room { get; set; }
public string slug { get; set; }
public string title { get; set; }
public string subtitle { get; set; }
public string track { get; set; }
public string type { get; set; }
public string language { get; set; }
public string @abstract { get; set; }
public string description { get; set; }
public string recording_license { get; set; }
public bool do_not_record { get; set; }
public List<Person> persons { get; set; }
public List<object> links { get; set; }
public List<object> attachments { get; set; }
public List<object> answers { get; set; }
}
public class Schedule {
public string url { get; set; }
public string version { get; set; }
public string base_url { get; set; }
public Conference conference { get; set; }
public string url { get; set; }
public string version { get; set; }
public string base_url { get; set; }
public Conference conference { get; set; }
}

@ -7,7 +7,7 @@ namespace ANSI_Fahrplan;
public class Playground {
public static void Run (string [] args) {
Console.WriteLine ("Program initializing Curses...");
Console.WriteLine ("Program initializing Curses...");
// var jsonString = File.ReadAllText ("schedule.json");
// var schedule = JsonConvert.DeserializeObject<Fahrplan.Root> (jsonString);
@ -18,22 +18,22 @@ public class Playground {
// Console.WriteLine ($" \\__ Room: {room.Key}");
// foreach (var ev in room.Value) {
// if (ev is null) continue;
// Console.WriteLine ($" \\__ Event: {ev.title}");
// Console.WriteLine ($" \\__ Event: {ev.title}");
// }
// }
// }
var screen = NCurses.InitScreen ();
NCurses.NoEcho ();
NCurses.CBreak ();
NCurses.SetCursor (0);
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 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);
@ -48,67 +48,67 @@ public class Playground {
NCurses.GetChar ();
NCurses.EndWin ();
/*var window = CreateWindowCentered (screen, 40, 12);
NCurses.WindowBackground (window, NCurses.ColorPair (1));
/*var window = CreateWindowCentered (screen, 40, 12);
NCurses.WindowBackground (window, NCurses.ColorPair (1));
NCurses.WindowAttributeOn (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");
//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 };
/*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 ();*/
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);
var inputTask = Task.Run (() => InputRoutine ());
while (inputTask.Status == TaskStatus.WaitingToRun);
while (inputTask.Status == TaskStatus.Running) {
NCurses.Refresh ();
//NCurses.WindowRefresh (window);
NCurses.Nap (1000 / 30);
}
while (inputTask.Status == TaskStatus.Running) {
NCurses.Refresh ();
//NCurses.WindowRefresh (window);
NCurses.Nap (1000 / 30);
}
//NCurses.AttributeOff (NCurses.ColorPair (1));
NCurses.EndWin ();
}
//NCurses.AttributeOff (NCurses.ColorPair (1));
NCurses.EndWin ();
}
static void InputRoutine () {
int chr = 0;
while (chr != CursesKey.ESC) {
chr = NCurses.GetChar ();
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 ();
/*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} ");
}*/
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.WindowBorder (window, ' ', ' ', ' ',' ',' ',' ',' ',' ');
NCurses.WindowRefresh (window);
NCurses.DeleteWindow (window);*/
NCurses.MoveAddString (10, 10, "Input routine exited");
NCurses.Refresh ();
NCurses.MoveAddString (10, 10, "Input routine exited");
NCurses.Refresh ();
return;
}
return;
}
}

@ -1,13 +1,12 @@
using SCI.CursesWrapper;
using SCI.CursesWrapper.UiElements;
using ANSI_Fahrplan.Screens;
using Mindmagma.Curses;
namespace ANSI_Fahrplan;
class Program {
private static void Main (string [] args) {
//Playground.Run (args);
private static void Main (string [] args) {
/**
* General procedure:
* - Draw welcome screen
@ -20,96 +19,76 @@ class Program {
**/
var screen = CursesWrapper.InitNCurses ();
//var topLevelWindows = new List<Window> ();
// -- Screen-wide input handler -- //
var inputHandler = new InputHandler (screen);
var inputHandler = new InputHandler ();
inputHandler.StartListening ();
// Register quit key on main screen
inputHandler.OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => {
if (e.SourceWindow is not null) return;
if (e.KeyCode != 'q') return;
// Register global quit key
inputHandler.OnKeyPressPrivileged += (object sender, NCursesKeyPressEventArgs e) => {
if (!CursesWrapper.KeyPressIsCtrl (e.KeyCode, 'q')) return;
NCurses.EndWin ();
Console.WriteLine ("Bye-bye!");
Environment.Exit (0);
};
// -- Create menu bar -- //
//var topMenu = new TopMenu (screen, CreateMenuItems (screen, screenInputHandler));
// -- Show introduction screen -- //
// NCurses.GetMaxYX (screen, out int height, out int width);
// var innerWindow = NCurses.NewWindow (height - 2, width - 4, 1, 2);
// NCurses.Box (innerWindow, (char) 0, (char) 0);
// -- Inner Window -- //
//
// This window contains all the dynamic content between
// the menu and status bars
var innerWindow = new InnerWindow (screen, inputHandler) {
BorderEnabled = true
};
var introScreen = new IntroScreen (screen);
introScreen.SetBorder (true);
introScreen.Redraw ();
// -- Intro screen -- //
var introScreen = new IntroScreen (innerWindow);
var parentWindow = new Window (3, 3, 10 * 2, 10) {
BackgroundColorId = ColorSchemes.TextInputField ()
// Close intro screen on any keypress
introScreen.OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => {
((Window) sender).Destroy ();
};
parentWindow.SetBorder (true);
parentWindow.Redraw ();
var childWindow = new Window (1, 1, 3, 3, parentWindow);
childWindow.SetBorder (true);
childWindow.Redraw ();
introScreen.RegisterInputHandler (inputHandler);
introScreen.SetWindowActive ();
inputHandler.OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => {
if (e.SourceWindow is not null) {
NCurses.WindowAddChar (e.SourceWindow.WindowId, e.KeyCode);
return;
} else {
NCurses.AddChar (e.KeyCode);
}
// Wait until intro screen is closed
while (introScreen.WindowId > -1) Thread.Sleep (50); // TODO; Unjank this
if (e.KeyCode != 'd') return;
parentWindow.Destroy ();
};
NCurses.AddString ("Done drawing");
NCurses.Refresh ();
// -- Create menu bar -- //
var topMenu = new TopMenu (screen, inputHandler, CreateMenuItems (innerWindow));
// Wait until the input handler routine stops
while (true) Thread.Sleep (500);
while (inputHandler.IsListening ()) Thread.Sleep (50);
NCurses.EndWin ();
Console.WriteLine ("Oh wow, the input handler crashed, that's not supposed to happen. Sorry!");
Environment.Exit (1);
}
// private static List<MenuItem> CreateMenuItems (nint screen, InputHandler inputHandler) {
// var helpItem = new MenuItem ("Help", "F1", inputHandler);
// var upcomingItem = new MenuItem ("Upcoming", "F2", inputHandler);
// var byDayItem = new MenuItem ("By Day", "F3", inputHandler);
// var byRoomItem = new MenuItem ("By Room", "F4", inputHandler);
// var bySpeakerItem = new MenuItem ("By Speaker", "F5", inputHandler);
// var quitItem = new MenuItem ("Quit", "q", inputHandler);
// quitItem.OnItemActivated += (object? sender, EventArgs e) => {
// NCurses.EndWin ();
// Console.WriteLine ("Bye-bye!");
// Environment.Exit (0);
// };
// helpItem.OnItemActivated += (object? sender, EventArgs e) => {
// InputBox.InputCompleted callback = (string ee) => {
// NCurses.MoveAddString (3, 3, $"<{ee}>");
// };
// new InputBox().RequestInput (screen, inputHandler, callback, "Please enter some text now:");
// };
// return new List<MenuItem> {
// helpItem,
// upcomingItem,
// byDayItem,
// byRoomItem,
// bySpeakerItem,
// quitItem
// };
// }
private static List<MenuItem> CreateMenuItems (InnerWindow innerWindow) {
var helpItem = new MenuItem ("Help", "F1");
var upcomingItem = new MenuItem ("Upcoming", "b"); //F2
var byDayItem = new MenuItem ("By Day", "n"); // F3
var byRoomItem = new MenuItem ("By Room", "F4");
var bySpeakerItem = new MenuItem ("By Speaker", "F5");
var byTestItem = new MenuItem ("By Test", "F7");
var quitItem = new MenuItem ("Quit (C-q)");
helpItem.OnItemActivated += (object sender, MenuItemActivatedEventArgs e) => {
};
return new List<MenuItem> {
helpItem,
upcomingItem,
byDayItem,
byRoomItem,
bySpeakerItem,
quitItem
};
}
}

@ -1,12 +0,0 @@
namespace ANSI_Fahrplan.Screens;
/// <summary>
/// NCurses Header Window
/// </summary>
public class Header {
public Header () {
}
}

@ -1,28 +1,26 @@
using Mindmagma.Curses;
using SCI.CursesWrapper;
namespace ANSI_Fahrplan.Screens;
public class IntroScreen : Window {
private nint infoTextBox;
/// <summary>
/// Pass class constructor through to inherited constructor
/// </summary>
/// <param name="parentWindow">Parent window of this window object</param>
public IntroScreen (nint screen) :
public IntroScreen (Window parentWindow) :
base (
1, 1,
CursesWrapper.GetWidth (screen) - 2,
CursesWrapper.GetHeight (screen) - 2
0, 0,
parentWindow.GetUsableWidth (),
parentWindow.GetUsableHeight (),
parentWindow
)
{
string asciiArtPlusText =
"Press F1 for a quick start guide or simply press Enter\n" +
"to see upcoming events\n" +
AsciiArt.logo38c3_80x24 +
"\n38C3 Fahrplan in your terminal!";
string asciiArtPlusText =
"Press F1 for a quick start guide or simply press Enter\n" +
"to see upcoming events\n" +
AsciiArt.logo38c3_80x24 +
"\n38C3 Fahrplan in your terminal!";
AsciiArt.ShowCentered (WindowId, asciiArtPlusText);
AsciiArt.ShowCentered (WindowId, asciiArtPlusText);
}
}

@ -14,8 +14,8 @@ public abstract class Screen {
private void PrepareWindow () {
NCurses.NoEcho ();
NCurses.CBreak ();
NCurses.SetCursor (0);
NCurses.CBreak ();
NCurses.SetCursor (0);
//NCurses.ClearWindow (RootWindow);
NCurses.WindowMove (RootWindow, 0, 0);

@ -2,7 +2,7 @@ namespace ANSI_Fahrplan.Screens;
public abstract class ScrollableScreen : Screen {
public ScrollableScreen (nint rootWindow) : base (rootWindow) {}
public ScrollableScreen (nint rootWindow) : base (rootWindow) {}
}

@ -1,18 +1,17 @@
using SCI.CursesWrapper;
using Mindmagma.Curses;
namespace ANSI_Fahrplan.UiElements;
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 boxPaddingTop = 1;
const int boxPaddingSides = 2;
const int boxPaddingBottom = 2;
const int boxMarginTopBottom = 1;
const int boxMarginSides = 2;
const int boxMarginSides = 2;
NCurses.GetMaxYX (screen, out int height, out int width);

@ -1,7 +1,6 @@
using SCI.CursesWrapper;
using Mindmagma.Curses;
namespace ANSI_Fahrplan.UiElements;
namespace SCI.CursesWrapper.UiElements;
public class MenuItem {
private List<MenuItem> _childItems;
@ -10,43 +9,75 @@ public class MenuItem {
private string _label;
public string Label { get { return _label; }}
private string _key;
public string Key { get { return _key; }}
private string? _key;
public string? Key { get { return _key; }}
public delegate void ItemActivated (object sender, EventArgs e);
public delegate void ItemActivated (object sender, MenuItemActivatedEventArgs e);
// Event fired when menu item is activated
public event EventHandler? OnItemActivated;
public event ItemActivated? OnItemActivated;
private InputHandler _inputHandler;
public InputHandler InputHandler { get { return _inputHandler; }}
private TopMenu? _parentTopMenuWindow;
public TopMenu? ParentTopMenuWindow { get { return _parentTopMenuWindow; }}
public MenuItem (string label, string key, InputHandler inputHandler, List<MenuItem>? childItems = null) {
/// <summary>
/// Class constructor
/// </summary>
/// <param name="label"></param>
/// <param name="key"></param>
/// <param name="childItems"></param>
public MenuItem (string label, string? key = null, List<MenuItem>? childItems = null) {
_childItems = childItems ?? new List<MenuItem> ();
_key = key;
_inputHandler = inputHandler;
_label = label;
}
public void KeyHandler (object sender, NCursesKeyPressEventArgs e) {
/// <summary>
/// Sets this menu items parent TopMenu window
/// </summary>
/// <param name="parent">TopMenu window this menu item belongs to</param>
public void AssignParentTopMenuWindow (TopMenu parent) {
if (_parentTopMenuWindow is not null) return;
_parentTopMenuWindow = parent;
}
/// <summary>
/// Called when key on parent TopMenu window is pressed
/// </summary>
/// <param name="sender">Event sender, should be a Window object</param>
/// <param name="e">Key Press Event arguments</param>
public void KeypressHandler (object sender, NCursesKeyPressEventArgs e) {
// Do not listen to key presses if not hotkey is assigned
if (Key is null) return;
// Do not react to key presses unless this menu items belongs to a window
// and has event listeners upon menu item activation
if (OnItemActivated 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 (e.KeyCode == CursesKey.KEY_F (int.Parse (Key.Substring (1)))) {
OnItemActivated (this, EventArgs.Empty);
OnItemActivated (this, eventArgs);
}
} else if (Key.Length == 1) { // Handle letters and numbers
if (e.KeyCode == Key [0]) {
OnItemActivated (this, EventArgs.Empty);
OnItemActivated (this, eventArgs);
}
} else throw new NotImplementedException ("Currently only F-keys and letters work for Top Menu actions");
}
}
public void RegisterKeyHandler () {
InputHandler.OnKeyPress += KeyHandler;
}
public class MenuItemActivatedEventArgs : EventArgs {
private TopMenu _topMenuWindow;
public TopMenu TopMenuWindow { get { return _topMenuWindow; }}
private MenuItem _relatedMenuItem;
public MenuItem RelatedMenuItem { get { return _relatedMenuItem; }}
public void UnregisterKeyHandler () {
InputHandler.OnKeyPress -= KeyHandler;
public MenuItemActivatedEventArgs (TopMenu sourceTopMenuWindow, MenuItem relatedMenuItem) {
_topMenuWindow = sourceTopMenuWindow;
_relatedMenuItem = relatedMenuItem;
}
}

@ -1,42 +1,49 @@
using SCI.CursesWrapper;
using Mindmagma.Curses;
using System.Collections.ObjectModel;
namespace ANSI_Fahrplan.UiElements;
namespace SCI.CursesWrapper.UiElements;
public class TopMenu : UiElement {
public class TopMenu : Window {
private List<MenuItem> _menuItems = new List<MenuItem> ();
public List<MenuItem> MenuItems {
public ReadOnlyCollection<MenuItem> MenuItems {
get {
return _menuItems;
}
set {
UnregisterItemEventHandlers ();
_menuItems = value;
RegisterItemEventHandlers ();
DrawMenu ();
return _menuItems.AsReadOnly ();
}
}
private MenuItem? activeMenuItem;
public TopMenu (nint screen, List<MenuItem> menuItems) : base (screen) {
NCurses.GetMaxYX (screen, out _, out int width);
innerWindow = NCurses.NewWindow (1, width, 0, 0);
/// <summary>
/// Class constructor
/// </summary>
/// <param name="screen"></param>
/// <param name="targetInputHandler"></param>
/// <param name="menuItems"></param>
public TopMenu (nint screen, InputHandler targetInputHandler, List<MenuItem> menuItems) :
base (0, 0, CursesWrapper.GetWidth (screen), 1, targetInputHandler)
{
var colorSchemeNormal = ColorSchemes.TopMenuNormal ();
NCurses.WindowBackground (innerWindow, colorSchemeNormal);
NCurses.WindowAttributeOn (innerWindow, colorSchemeNormal);
BackgroundColorId = colorSchemeNormal;
NCurses.WindowAttributeOn (WindowId, colorSchemeNormal);
_menuItems = menuItems;
foreach (var item in menuItems) {
item.AssignParentTopMenuWindow (this);
}
MenuItems = menuItems;
RegisterItemEventHandlers ();
DrawMenu ();
}
/// <summary>
/// Register key event handlers for each menu item
/// </summary>
private void RegisterItemEventHandlers () {
if (TargetInputHandler is null) return;
foreach (var item in MenuItems) {
item.OnItemActivated += ItemActivatedHandler;
item.RegisterKeyHandler ();
TargetInputHandler.OnKeyPressPrivileged += item.KeypressHandler;
}
}
@ -44,9 +51,11 @@ public class TopMenu : UiElement {
/// Unregister key event handlers for each menu item
/// </summary>
private void UnregisterItemEventHandlers () {
if (TargetInputHandler is null) return;
foreach (var item in MenuItems) {
item.OnItemActivated -= ItemActivatedHandler;
item.UnregisterKeyHandler ();
OnKeyPress -= item.KeypressHandler;
}
}
@ -66,7 +75,7 @@ public class TopMenu : UiElement {
/// Draw the top menu with currently configured menu items
/// </summary>
private void DrawMenu () {
NCurses.ClearWindow (innerWindow);
NCurses.ClearWindow (WindowId);
var normalColor = ColorSchemes.TopMenuNormal ();
var activeColor = ColorSchemes.TopMenuActive ();
@ -77,8 +86,8 @@ public class TopMenu : UiElement {
if (itemIsActive) {
itemString = "[";
NCurses.WindowAttributeOff (innerWindow, normalColor);
NCurses.WindowAttributeOn (innerWindow, activeColor);
NCurses.WindowAttributeOff (WindowId, normalColor);
NCurses.WindowAttributeOn (WindowId, activeColor);
}
itemString += $"{item.Label}";
@ -88,21 +97,16 @@ public class TopMenu : UiElement {
}
if (itemIsActive) {
NCurses.WindowAttributeOff (innerWindow, activeColor);
NCurses.WindowAttributeOn (innerWindow, normalColor);
NCurses.WindowAttributeOff (WindowId, activeColor);
NCurses.WindowAttributeOn (WindowId, normalColor);
itemString += "] ";
} else {
itemString += " ";
}
NCurses.WindowAddString (innerWindow, itemString);
NCurses.WindowAddString (WindowId, itemString);
}
NCurses.Refresh ();
NCurses.WindowRefresh (innerWindow);
}
public void Refresh () {
DrawMenu ();
Draw ();
}
}

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="dotnet-curses" Version="1.0.3" />
<PackageReference Include="dotnet-curses" Version="1.0.3" />
</ItemGroup>
</Project>

Loading…
Cancel
Save