diff --git a/NCursesColorSchemes.cs b/CursesWrapper/ColorSchemes.cs
similarity index 95%
rename from NCursesColorSchemes.cs
rename to CursesWrapper/ColorSchemes.cs
index 64c5be9..1d7f8ff 100644
--- a/NCursesColorSchemes.cs
+++ b/CursesWrapper/ColorSchemes.cs
@@ -1,6 +1,6 @@
using Mindmagma.Curses;
-namespace ANSI_Fahrplan;
+namespace SCI.CursesWrapper;
public class ColorSchemes {
public static void InitAll () {
diff --git a/CursesWrapper/CursesWrapper.cs b/CursesWrapper/CursesWrapper.cs
new file mode 100644
index 0000000..b539e5d
--- /dev/null
+++ b/CursesWrapper/CursesWrapper.cs
@@ -0,0 +1,41 @@
+using Mindmagma.Curses;
+
+namespace SCI.CursesWrapper;
+
+public class CursesWrapper {
+ ///
+ /// Initializes NCurses and creates a screen
+ ///
+ public static nint InitNCurses () {
+ var screen = NCurses.InitScreen ();
+ NCurses.SetCursor (0);
+ NCurses.NoEcho ();
+
+ if (NCurses.HasColors ()) {
+ NCurses.StartColor ();
+ ColorSchemes.InitAll ();
+ }
+
+ return screen;
+ }
+
+ ///
+ /// Get the total width of a window
+ ///
+ /// Window to query
+ /// Width of window in columns
+ public static int GetWidth (nint window) {
+ NCurses.GetMaxYX (window, out int _, out int width);
+ return width;
+ }
+
+ ///
+ /// Get the total height of a window
+ ///
+ /// Window to query
+ /// Height of window in rows
+ public static int GetHeight (nint window) {
+ NCurses.GetMaxYX (window, out int height, out int _);
+ return height;
+ }
+}
\ No newline at end of file
diff --git a/InputHandler.cs b/CursesWrapper/InputHandler.cs
similarity index 55%
rename from InputHandler.cs
rename to CursesWrapper/InputHandler.cs
index 6b17fd3..80e1b04 100644
--- a/InputHandler.cs
+++ b/CursesWrapper/InputHandler.cs
@@ -1,7 +1,6 @@
-using System.Security.Principal;
using Mindmagma.Curses;
-namespace ANSI_Fahrplan;
+namespace SCI.CursesWrapper;
public class InputHandler {
///
@@ -11,35 +10,54 @@ public class InputHandler {
/// Event arguments with details like the pressed key code
public delegate void KeypressEventHandler (object sender, NCursesKeyPressEventArgs e);
- // Event fired when key is pressed
+ ///
+ /// Event handlers for NCurses key presses
+ ///
public event KeypressEventHandler? OnKeyPress;
- // Similar to OnKeyPress, but called before any regular OnKeyPress event handlers are called
+ ///
+ /// Event Handlers called before the regular OnKeyPress handlers are called
+ ///
public event KeypressEventHandler? OnKeyPressPrivileged;
- // When this variable is set, no event handlers will be called except this one
- // Once e.CancelNextEvent is true from this event, it will be set to null again
- private KeypressEventHandler? _rawKeyPressHandler;
- public KeypressEventHandler? RawKeyPressHandler { get { return _rawKeyPressHandler; }}
-
- private nint _targetWindow;
- public nint TargetWindow { get { return _targetWindow; }}
+ ///
+ /// Gets or sets the window this InputHandler is listening to
+ ///
+ private Window? _activeWindow;
+ public Window? ActiveWindow {
+ get {
+ return _activeWindow;
+ }
+ set {
+ _activeWindow = value;
+ }
+ }
- private CancellationTokenSource? listeningRoutineCts;
+ ///
+ /// Root screen to fall back to when no window is active
+ ///
+ private nint rootScreen;
+ ///
+ /// Task running the endless input handler routine
+ ///
private Task? listeningTask;
+ ///
+ /// Cancellation Token Source to stop the listening task
+ ///
+ private CancellationTokenSource? listeningRoutineCts;
+
///
/// Class constructor
+ /// Parent screen this InputHandler is attached to
///
- /// Window for which to listen to key presses
- public InputHandler (nint targetWindow) {
- _targetWindow = targetWindow;
+ public InputHandler (nint screen) {
+ rootScreen = screen;
}
///
/// Start listening to key presses and call registered callbacks
- /// Window to listen to key presses in
///
public void StartListening () {
if (IsListening ()) return;
@@ -62,7 +80,9 @@ public class InputHandler {
listeningTask.Wait ();
listeningTask = null;
- NCurses.Keypad (TargetWindow, false);
+ if (ActiveWindow is not null) {
+ NCurses.Keypad (ActiveWindow.WindowId, false);
+ }
}
///
@@ -77,43 +97,29 @@ public class InputHandler {
}
///
- /// Enable temporary raw keypress event handler to forward
- /// all keypressed directly to a specific function
+ /// Prepares the window for proper key press reading
///
- /// Event handler to pass key presses to
- /// Screen to pass key char duty to
- public void EnableRawEventHandler (KeypressEventHandler eventHandler, nint? window = null) {
- if (window is not null) {
- StopListening ();
- _targetWindow = (nint) window;
- StartListening ();
+ 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);
}
-
- _rawKeyPressHandler = eventHandler;
}
///
- /// Disables the temporary raw keypress event handler
- /// Screen to pass key char duty back to
+ /// Undoes any changes the setup routine configured for the active window
///
- public void DisableRawEventHandler (nint? screen = null) {
- if (RawKeyPressHandler is null) return;
-
- if (screen is not null) {
- StopListening ();
- _targetWindow = (nint) screen;
- StartListening ();
+ 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);
}
-
- _rawKeyPressHandler = null;
- }
-
- ///
- /// Prepares the window for proper key press reading
- ///
- private void WindowSetup () {
- NCurses.Keypad (TargetWindow, true);
- NCurses.NoDelay (TargetWindow, false);
}
///
@@ -124,35 +130,38 @@ public class InputHandler {
WindowSetup ();
+ int keyCode = -1;
+
while (!listeningRoutineCts.Token.IsCancellationRequested) {
- int keyCode = NCurses.WindowGetChar (TargetWindow);
+ if (OnKeyPress is null) {
+ Console.Title = "No handlers";
+ } else {
+ Console.Title = $"Got {OnKeyPress.GetInvocationList ().Length} handlers";
+ }
+
+ if (ActiveWindow is not null) {
+ keyCode = NCurses.WindowGetChar (ActiveWindow.WindowId);
+ } else {
+ keyCode = NCurses.GetChar ();
+ }
// Do nothing until the key code is larger than -1
if (keyCode < 0) continue;
- var eventArgs = new NCursesKeyPressEventArgs (keyCode, TargetWindow);
-
- // If enabled, forward all key pressed directly to raw keypress event handler
- if (RawKeyPressHandler is not null) {
- RawKeyPressHandler (this, eventArgs);
- continue;
- }
-
- var skipRegularEventHandlers = false;
-
+ var eventArgs = new NCursesKeyPressEventArgs (keyCode, ActiveWindow);
+
// Handle any registered privileged key handlers
if (OnKeyPressPrivileged is not null) {
foreach (KeypressEventHandler h in OnKeyPressPrivileged.GetInvocationList ()) {
h (this, eventArgs);
if (eventArgs.CancelNextEvent == true) {
- skipRegularEventHandlers = true;
break;
}
}
}
// Handle any regular registered key handlers
- if (OnKeyPress is not null && !skipRegularEventHandlers) {
+ if (OnKeyPress is not null) {
foreach (KeypressEventHandler h in OnKeyPress.GetInvocationList ()) {
h (this, eventArgs);
if (eventArgs.CancelNextEvent == true) continue ;
@@ -169,13 +178,13 @@ public class NCursesKeyPressEventArgs : EventArgs {
private int _keyCode;
public int KeyCode { get { return _keyCode; }}
- private nint _targetWindow;
- public nint TargetWindow { get { return _targetWindow; }}
+ private Window? _sourceWindow;
+ public Window? SourceWindow { get { return _sourceWindow; }}
public bool CancelNextEvent { get; set; } = false;
- public NCursesKeyPressEventArgs (int keyCode, nint targetWindow) {
+ public NCursesKeyPressEventArgs (int keyCode, Window? sourceWindow) {
_keyCode = keyCode;
- _targetWindow = targetWindow;
+ _sourceWindow = sourceWindow;
}
}
\ No newline at end of file
diff --git a/CursesWrapper/Window.cs b/CursesWrapper/Window.cs
new file mode 100644
index 0000000..ecc13d4
--- /dev/null
+++ b/CursesWrapper/Window.cs
@@ -0,0 +1,265 @@
+using System.Collections.ObjectModel;
+using System.Drawing;
+using Mindmagma.Curses;
+
+namespace SCI.CursesWrapper;
+
+public class Window {
+ ///
+ /// Gets or sets the window position on the screen
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Gets or sets the width and height of the window
+ ///
+ private Size _windowSize;
+ public Size WindowSize {
+ get {
+ return _windowSize;
+ }
+ set {
+ _windowSize = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the window background color pair
+ ///
+ private uint _backgroundColorId;
+ public uint BackgroundColorId {
+ get {
+ return _backgroundColorId;
+ }
+ set {
+ NCurses.WindowBackground (WindowId, value);
+ Redraw ();
+ _backgroundColorId = value;
+ }
+ }
+
+ ///
+ /// Sets the parent window
+ ///
+ private Window? _parentWindow;
+ public Window? ParentWindow { get { return _parentWindow; }}
+
+ ///
+ /// Holds a list of children windows this window posesses
+ ///
+ private List _childWindows = new List ();
+ public ReadOnlyCollection ChildWindows { get { return _childWindows.AsReadOnly (); }}
+
+ ///
+ /// Holds the pointer for this window
+ ///
+ private nint _windowId;
+ public nint WindowId { get { return _windowId; }}
+
+ ///
+ /// Input handler assigned to this window
+ ///
+ private InputHandler? inputHandler;
+
+ ///
+ /// Event handler called when this window is active and a key is pressed
+ ///
+ public event InputHandler.KeypressEventHandler? OnKeyPress;
+
+ ///
+ /// Create new window by specifying X/Y and Width/Height geometry
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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 ();
+ }
+
+ ///
+ /// Create new window by specifying geometry through Point and Size objects
+ ///
+ ///
+ ///
+ ///
+ 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 ();
+ }
+
+ ///
+ /// Adds a child window to this window
+ ///
+ ///
+ public void AddChildWindow (Window child) {
+ if (_childWindows.Contains (child)) return;
+ _childWindows.Add (child);
+ }
+
+ ///
+ /// Removes a child window from this window
+ ///
+ ///
+ public void RemoveChildWindow (Window child) {
+ if (!_childWindows.Contains (child)) return;
+ _childWindows.Remove (child);
+ }
+
+ ///
+ /// Discards all optimization options about drawn parts of this window.
+ /// Call before drawing a sub window
+ ///
+ public void TouchWin () {
+ NCurses.TouchWindow (WindowId);
+ }
+
+ ///
+ /// Redraws this window
+ ///
+ 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);
+ }
+
+ ///
+ /// Destroys this window and all children windows
+ ///
+ 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
+ }
+
+ ///
+ /// Register an input handler for this window to attach to OnKeyPress events
+ ///
+ /// InputHandler to register
+ public void RegisterInputHandler (InputHandler targetInputHandler) {
+ if (inputHandler is not null) throw new Exception (
+ "Another input handler is already registered"
+ );
+
+ inputHandler = targetInputHandler;
+ inputHandler.OnKeyPress += KeyPressHandler;
+ }
+
+ ///
+ /// Detach from all OnKeyPress events and unset input handler
+ ///
+ public void UnregisterInputHandler () {
+ if (inputHandler is null) return;
+
+ inputHandler.OnKeyPress -= KeyPressHandler;
+ }
+
+ ///
+ /// Handle key press events from the input handler
+ ///
+ ///
+ ///
+ private void KeyPressHandler (object sender, NCursesKeyPressEventArgs e) {
+ if (e.SourceWindow != this) return;
+
+ if (OnKeyPress is not null) {
+ OnKeyPress (sender, e);
+ }
+ }
+
+ ///
+ /// Tells the input handler this window is active
+ ///
+ public void SetWindowActive () {
+ if (inputHandler is null) return;
+
+ inputHandler.ActiveWindow = this;
+ }
+
+ ///
+ /// Enables or disables a border around this window
+ ///
+ /// Sets the status of the border
+ /// If specified, uses this character as the top and bottom border
+ /// If specified, uses this character as the left and right border
+ 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, ' ', ' ');
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
index d8eb55e..90fb10b 100755
--- a/Program.cs
+++ b/Program.cs
@@ -1,5 +1,5 @@
-using ANSI_Fahrplan.Screens;
-using ANSI_Fahrplan.UiElements;
+using SCI.CursesWrapper;
+using ANSI_Fahrplan.Screens;
using Mindmagma.Curses;
namespace ANSI_Fahrplan;
@@ -19,31 +19,60 @@ class Program {
* - User can change the list type with function keys
**/
- var screen = NCurses.InitScreen ();
- NCurses.SetCursor (0);
- NCurses.NoEcho ();
-
- var hasColors = NCurses.HasColors ();
- if (hasColors) {
- NCurses.StartColor ();
- ColorSchemes.InitAll ();
- }
+ var screen = CursesWrapper.InitNCurses ();
// -- Screen-wide input handler -- //
- var screenInputHandler = new InputHandler (screen);
- screenInputHandler.StartListening ();
+ var inputHandler = new InputHandler (screen);
+ 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;
+
+ NCurses.EndWin ();
+ Console.WriteLine ("Bye-bye!");
+ Environment.Exit (0);
+ };
// -- Create menu bar -- //
- var topMenu = new TopMenu (screen, CreateMenuItems (screen, screenInputHandler));
+ //var topMenu = new TopMenu (screen, CreateMenuItems (screen, screenInputHandler));
// -- Show introduction screen -- //
- NCurses.GetMaxYX (screen, out int height, out int width);
+ // 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);
+
+ var introScreen = new IntroScreen (screen);
+ introScreen.SetBorder (true);
+ introScreen.Redraw ();
+
+ var parentWindow = new Window (3, 3, 10 * 2, 10) {
+ BackgroundColorId = ColorSchemes.TextInputField ()
+ };
+ parentWindow.SetBorder (true);
+ parentWindow.Redraw ();
- var innerWindow = NCurses.NewWindow (height - 2, width - 4, 1, 2);
- NCurses.Box (innerWindow, (char) 0, (char) 0);
+ var childWindow = new Window (1, 1, 3, 3, parentWindow);
+ childWindow.SetBorder (true);
+ childWindow.Redraw ();
- //var introScreen = new IntroScreen (innerWindow);
- //introScreen.Show ();
+ 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);
+ }
+
+ if (e.KeyCode != 'd') return;
+
+ parentWindow.Destroy ();
+ };
+
+ NCurses.AddString ("Done drawing");
+ NCurses.Refresh ();
// Wait until the input handler routine stops
while (true) Thread.Sleep (500);
@@ -53,34 +82,34 @@ class Program {
Environment.Exit (1);
}
- private static List