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

main
resneptacle 4 weeks ago
parent 04677ab97a
commit a6377d1a57

@ -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
}
}
}

@ -9,7 +9,11 @@ public class CursesWrapper {
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 ()) {
NCurses.StartColor ();
@ -20,7 +24,7 @@ public class CursesWrapper {
}
/// <summary>
/// Get the total width of a window
/// Get the total width of a window/screen
/// </summary>
/// <param name="window">Window to query</param>
/// <returns>Width of window in columns</returns>
@ -38,4 +42,15 @@ public class CursesWrapper {
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 either key code is larger than -1
if (keyCode < 0 && keyCode2 < 0) continue;
// Do nothing until the key code is larger than -1
if (keyCode < 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,6 +5,8 @@ using Mindmagma.Curses;
namespace SCI.CursesWrapper;
public class Window {
public const int InnerPadding = 1;
/// <summary>
/// Gets or sets the window position on the screen
/// </summary>
@ -15,10 +17,13 @@ public class Window {
}
set {
if (ParentWindow is not null) {
int addedPadding = ParentWindow.BorderEnabled?
InnerPadding + 1 : 0;
NCurses.WindowMove (
WindowId,
ParentWindow.Position.Y + value.Y,
ParentWindow.Position.X + value.X
ParentWindow.Position.Y + value.Y + addedPadding,
ParentWindow.Position.X + value.X + addedPadding
);
} else {
NCurses.WindowMove (
@ -54,11 +59,23 @@ public class Window {
}
set {
NCurses.WindowBackground (WindowId, value);
Redraw ();
Draw ();
_backgroundColorId = value;
}
}
private bool _borderEnabled;
public bool BorderEnabled {
get {
return _borderEnabled;
}
set {
SetBorder (value);
Draw ();
_borderEnabled = value;
}
}
/// <summary>
/// Sets the parent window
/// </summary>
@ -80,7 +97,8 @@ public class Window {
/// <summary>
/// Input handler assigned to this window
/// </summary>
private InputHandler? inputHandler;
protected InputHandler? _targetInputHandler;
public InputHandler? TargetInputHandler { get { return _targetInputHandler; }}
/// <summary>
/// Event handler called when this window is active and a key is pressed
@ -96,20 +114,32 @@ public class Window {
/// <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
);
_Initialize (new Point (x, y), new Size (width, height), parentWindow);
}
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) {
_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>
@ -117,14 +147,32 @@ public class Window {
/// </summary>
/// <param name="position"></param>
/// <param name="windowSize"></param>
/// <param name="targetInputHandler"></param>
/// <param name="parentWindow"></param>
public Window (Point position, Size windowSize, Window? parentWindow = null) {
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) {
_windowId = NCurses.SubWindow (
int addedPadding = parentWindow.BorderEnabled?
InnerPadding + 1 : 0;
_windowId = NCurses.DeriveWindow (
parentWindow.WindowId,
windowSize.Height, windowSize.Width,
position.Y, position.X
position.Y + addedPadding, position.X + addedPadding
);
parentWindow.AddChildWindow (this);
} else {
_windowId = NCurses.NewWindow (
windowSize.Height, windowSize.Width,
@ -132,7 +180,20 @@ public class Window {
);
}
Redraw ();
_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>
@ -149,7 +210,6 @@ public class Window {
/// </summary>
/// <param name="child"></param>
public void RemoveChildWindow (Window child) {
if (!_childWindows.Contains (child)) return;
_childWindows.Remove (child);
}
@ -162,15 +222,13 @@ public class Window {
}
/// <summary>
/// Redraws this window
/// Draws this window and all sub windows
/// </summary>
public void Redraw () {
public void Draw () {
NCurses.Refresh (); // TODO: Necessary?
if (ChildWindows.Count > 0) {
foreach (var window in ChildWindows) {
window.Redraw ();
}
window.Draw ();
}
if (ParentWindow is not null) ParentWindow.TouchWin ();
@ -181,47 +239,54 @@ public class Window {
/// Destroys this window and all children windows
/// </summary>
public void Destroy () {
if (ChildWindows.Count > 0) {
foreach (var window in _childWindows) {
// Catch double destroy calls
if (WindowId == -1) throw new Exception ("Destroy called twice on object");
foreach (var window in _childWindows.ToList ()) {
window.Destroy ();
}
}
if (inputHandler is not null) inputHandler.ActiveWindow = null;
if (ParentWindow is not null) ParentWindow.RemoveChildWindow (this);
UnregisterInputHandler ();
// Prepare for screen clear
NCurses.WindowBackground (WindowId, NCurses.ColorPair (-1));
SetBorder (false);
// Clear window and redraw
NCurses.ClearWindow (WindowId);
Console.Title = "About to destroy";
Draw ();
NCurses.DeleteWindow (WindowId);
Console.Title = "Destroyed";
_windowId = -1;
//TODO: Program hangs on DeleteWindow
// 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="inputHandler">InputHandler to register</param>
public void RegisterInputHandler (InputHandler targetInputHandler) {
if (inputHandler is not null) throw new Exception (
/// <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"
);
inputHandler = targetInputHandler;
inputHandler.OnKeyPress += KeyPressHandler;
_targetInputHandler = inputHandler;
TargetInputHandler!.OnKeyPress += KeyPressHandler;
}
/// <summary>
/// Detach from all OnKeyPress events and unset input handler
/// </summary>
public void UnregisterInputHandler () {
if (inputHandler is null) return;
if (TargetInputHandler is null) return;
inputHandler.OnKeyPress -= KeyPressHandler;
if (TargetInputHandler.ActiveWindow == this) TargetInputHandler.ActiveWindow = null;
TargetInputHandler.OnKeyPress -= KeyPressHandler;
}
/// <summary>
@ -233,7 +298,7 @@ public class Window {
if (e.SourceWindow != this) return;
if (OnKeyPress is not null) {
OnKeyPress (sender, e);
OnKeyPress (this, e);
}
}
@ -241,9 +306,10 @@ public class Window {
/// Tells the input handler this window is active
/// </summary>
public void SetWindowActive () {
if (inputHandler is null) return;
if (TargetInputHandler is null) return;
inputHandler.ActiveWindow = this;
TargetInputHandler.ActiveWindow = this;
Draw ();
}
/// <summary>
@ -262,4 +328,28 @@ public class Window {
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;
}
}
}

@ -1,4 +1,5 @@
using SCI.CursesWrapper;
using SCI.CursesWrapper.UiElements;
using ANSI_Fahrplan.Screens;
using Mindmagma.Curses;
@ -6,8 +7,6 @@ namespace ANSI_Fahrplan;
class Program {
private static void Main (string [] args) {
//Playground.Run (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));
// -- Inner Window -- //
//
// This window contains all the dynamic content between
// the menu and status bars
var innerWindow = new InnerWindow (screen, inputHandler) {
BorderEnabled = true
};
// -- Show introduction screen -- //
// NCurses.GetMaxYX (screen, out int height, out int width);
// -- Intro screen -- //
var introScreen = new IntroScreen (innerWindow);
// var innerWindow = NCurses.NewWindow (height - 2, width - 4, 1, 2);
// NCurses.Box (innerWindow, (char) 0, (char) 0);
// Close intro screen on any keypress
introScreen.OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => {
((Window) sender).Destroy ();
};
var introScreen = new IntroScreen (screen);
introScreen.SetBorder (true);
introScreen.Redraw ();
introScreen.RegisterInputHandler (inputHandler);
introScreen.SetWindowActive ();
var parentWindow = new Window (3, 3, 10 * 2, 10) {
BackgroundColorId = ColorSchemes.TextInputField ()
};
parentWindow.SetBorder (true);
parentWindow.Redraw ();
var childWindow = new Window (1, 1, 3, 3, parentWindow);
childWindow.SetBorder (true);
childWindow.Redraw ();
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,20 +1,18 @@
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 =

@ -1,7 +1,6 @@
using SCI.CursesWrapper;
using Mindmagma.Curses;
namespace ANSI_Fahrplan.UiElements;
namespace SCI.CursesWrapper.UiElements;
public class InputBox {
public delegate void InputCompleted (string input);

@ -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;
_menuItems = menuItems;
foreach (var item in menuItems) {
item.AssignParentTopMenuWindow (this);
}
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 ();
}
}
Loading…
Cancel
Save