Began adding Window class

main
resneptacle 4 weeks ago
parent fa4fd92cdf
commit 04677ab97a

@ -1,6 +1,6 @@
using Mindmagma.Curses; using Mindmagma.Curses;
namespace ANSI_Fahrplan; namespace SCI.CursesWrapper;
public class ColorSchemes { public class ColorSchemes {
public static void InitAll () { public static void InitAll () {

@ -0,0 +1,41 @@
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 ();
NCurses.SetCursor (0);
NCurses.NoEcho ();
if (NCurses.HasColors ()) {
NCurses.StartColor ();
ColorSchemes.InitAll ();
}
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 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;
}
}

@ -1,7 +1,6 @@
using System.Security.Principal;
using Mindmagma.Curses; using Mindmagma.Curses;
namespace ANSI_Fahrplan; namespace SCI.CursesWrapper;
public class InputHandler { public class InputHandler {
/// <summary> /// <summary>
@ -11,35 +10,54 @@ public class InputHandler {
/// <param name="e">Event arguments with details like the pressed key code</param> /// <param name="e">Event arguments with details like the pressed key code</param>
public delegate void KeypressEventHandler (object sender, NCursesKeyPressEventArgs e); public delegate void KeypressEventHandler (object sender, NCursesKeyPressEventArgs e);
// Event fired when key is pressed /// <summary>
/// Event handlers for NCurses key presses
/// </summary>
public event KeypressEventHandler? OnKeyPress; public event KeypressEventHandler? OnKeyPress;
// Similar to OnKeyPress, but called before any regular OnKeyPress event handlers are called /// <summary>
/// Event Handlers called before the regular OnKeyPress handlers are called
/// </summary>
public event KeypressEventHandler? OnKeyPressPrivileged; public event KeypressEventHandler? OnKeyPressPrivileged;
// When this variable is set, no event handlers will be called except this one /// <summary>
// Once e.CancelNextEvent is true from this event, it will be set to null again /// Gets or sets the window this InputHandler is listening to
private KeypressEventHandler? _rawKeyPressHandler; /// </summary>
public KeypressEventHandler? RawKeyPressHandler { get { return _rawKeyPressHandler; }} private Window? _activeWindow;
public Window? ActiveWindow {
private nint _targetWindow; get {
public nint TargetWindow { get { return _targetWindow; }} return _activeWindow;
}
set {
_activeWindow = value;
}
}
private CancellationTokenSource? listeningRoutineCts; /// <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>
private Task? listeningTask; private Task? listeningTask;
/// <summary>
/// Cancellation Token Source to stop the listening task
/// </summary>
private CancellationTokenSource? listeningRoutineCts;
/// <summary> /// <summary>
/// Class constructor /// Class constructor
/// <paramref name="screen">Parent screen this InputHandler is attached to</paramref>
/// </summary> /// </summary>
/// <param name="targetWindow">Window for which to listen to key presses</param> public InputHandler (nint screen) {
public InputHandler (nint targetWindow) { rootScreen = screen;
_targetWindow = targetWindow;
} }
/// <summary> /// <summary>
/// Start listening to key presses and call registered callbacks /// Start listening to key presses and call registered callbacks
/// <paramref name="window">Window to listen to key presses in<param/>
/// </summary> /// </summary>
public void StartListening () { public void StartListening () {
if (IsListening ()) return; if (IsListening ()) return;
@ -62,7 +80,9 @@ public class InputHandler {
listeningTask.Wait (); listeningTask.Wait ();
listeningTask = null; listeningTask = null;
NCurses.Keypad (TargetWindow, false); if (ActiveWindow is not null) {
NCurses.Keypad (ActiveWindow.WindowId, false);
}
} }
/// <summary> /// <summary>
@ -77,43 +97,29 @@ public class InputHandler {
} }
/// <summary> /// <summary>
/// Enable temporary raw keypress event handler to forward /// Prepares the window for proper key press reading
/// all keypressed directly to a specific function
/// </summary> /// </summary>
/// <param name="eventHandler">Event handler to pass key presses to</param> private void WindowSetup () {
/// <param name="screen">Screen to pass key char duty to</param> if (ActiveWindow is not null) {
public void EnableRawEventHandler (KeypressEventHandler eventHandler, nint? window = null) { NCurses.Keypad (ActiveWindow.WindowId, true);
if (window is not null) { NCurses.NoDelay (ActiveWindow.WindowId, false);
StopListening (); } else {
_targetWindow = (nint) window; NCurses.Keypad (rootScreen, true);
StartListening (); NCurses.NoDelay (rootScreen, false);
} }
_rawKeyPressHandler = eventHandler;
} }
/// <summary> /// <summary>
/// Disables the temporary raw keypress event handler /// Undoes any changes the setup routine configured for the active window
/// <param name="screen">Screen to pass key char duty back to</param>
/// </summary> /// </summary>
public void DisableRawEventHandler (nint? screen = null) { private void WindowTeardown () {
if (RawKeyPressHandler is null) return; if (ActiveWindow is not null) {
NCurses.Keypad (ActiveWindow.WindowId, false);
if (screen is not null) { NCurses.NoDelay (ActiveWindow.WindowId, true);
StopListening (); } else {
_targetWindow = (nint) screen; NCurses.Keypad (rootScreen, false);
StartListening (); NCurses.NoDelay (rootScreen, true);
} }
_rawKeyPressHandler = null;
}
/// <summary>
/// Prepares the window for proper key press reading
/// </summary>
private void WindowSetup () {
NCurses.Keypad (TargetWindow, true);
NCurses.NoDelay (TargetWindow, false);
} }
/// <summary> /// <summary>
@ -124,35 +130,38 @@ public class InputHandler {
WindowSetup (); WindowSetup ();
int keyCode = -1;
while (!listeningRoutineCts.Token.IsCancellationRequested) { 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 // Do nothing until the key code is larger than -1
if (keyCode < 0) continue; if (keyCode < 0) continue;
var eventArgs = new NCursesKeyPressEventArgs (keyCode, TargetWindow); var eventArgs = new NCursesKeyPressEventArgs (keyCode, ActiveWindow);
// If enabled, forward all key pressed directly to raw keypress event handler
if (RawKeyPressHandler is not null) {
RawKeyPressHandler (this, eventArgs);
continue;
}
var skipRegularEventHandlers = false;
// Handle any registered privileged key handlers // Handle any registered privileged key handlers
if (OnKeyPressPrivileged is not null) { if (OnKeyPressPrivileged is not null) {
foreach (KeypressEventHandler h in OnKeyPressPrivileged.GetInvocationList ()) { foreach (KeypressEventHandler h in OnKeyPressPrivileged.GetInvocationList ()) {
h (this, eventArgs); h (this, eventArgs);
if (eventArgs.CancelNextEvent == true) { if (eventArgs.CancelNextEvent == true) {
skipRegularEventHandlers = true;
break; break;
} }
} }
} }
// Handle any regular registered key handlers // Handle any regular registered key handlers
if (OnKeyPress is not null && !skipRegularEventHandlers) { if (OnKeyPress is not null) {
foreach (KeypressEventHandler h in OnKeyPress.GetInvocationList ()) { foreach (KeypressEventHandler h in OnKeyPress.GetInvocationList ()) {
h (this, eventArgs); h (this, eventArgs);
if (eventArgs.CancelNextEvent == true) continue ; if (eventArgs.CancelNextEvent == true) continue ;
@ -169,13 +178,13 @@ public class NCursesKeyPressEventArgs : EventArgs {
private int _keyCode; private int _keyCode;
public int KeyCode { get { return _keyCode; }} public int KeyCode { get { return _keyCode; }}
private nint _targetWindow; private Window? _sourceWindow;
public nint TargetWindow { get { return _targetWindow; }} public Window? SourceWindow { get { return _sourceWindow; }}
public bool CancelNextEvent { get; set; } = false; public bool CancelNextEvent { get; set; } = false;
public NCursesKeyPressEventArgs (int keyCode, nint targetWindow) { public NCursesKeyPressEventArgs (int keyCode, Window? sourceWindow) {
_keyCode = keyCode; _keyCode = keyCode;
_targetWindow = targetWindow; _sourceWindow = sourceWindow;
} }
} }

@ -0,0 +1,265 @@
using System.Collections.ObjectModel;
using System.Drawing;
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, ' ', ' ');
}
}
}

@ -1,5 +1,5 @@
using ANSI_Fahrplan.Screens; using SCI.CursesWrapper;
using ANSI_Fahrplan.UiElements; using ANSI_Fahrplan.Screens;
using Mindmagma.Curses; using Mindmagma.Curses;
namespace ANSI_Fahrplan; namespace ANSI_Fahrplan;
@ -19,31 +19,60 @@ class Program {
* - User can change the list type with function keys * - User can change the list type with function keys
**/ **/
var screen = NCurses.InitScreen (); var screen = CursesWrapper.InitNCurses ();
NCurses.SetCursor (0);
NCurses.NoEcho ();
var hasColors = NCurses.HasColors ();
if (hasColors) {
NCurses.StartColor ();
ColorSchemes.InitAll ();
}
// -- Screen-wide input handler -- // // -- Screen-wide input handler -- //
var screenInputHandler = new InputHandler (screen); var inputHandler = new InputHandler (screen);
screenInputHandler.StartListening (); 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 -- // // -- Create menu bar -- //
var topMenu = new TopMenu (screen, CreateMenuItems (screen, screenInputHandler)); //var topMenu = new TopMenu (screen, CreateMenuItems (screen, screenInputHandler));
// -- Show introduction screen -- // // -- 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 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);
}
var innerWindow = NCurses.NewWindow (height - 2, width - 4, 1, 2); if (e.KeyCode != 'd') return;
NCurses.Box (innerWindow, (char) 0, (char) 0);
//var introScreen = new IntroScreen (innerWindow); parentWindow.Destroy ();
//introScreen.Show (); };
NCurses.AddString ("Done drawing");
NCurses.Refresh ();
// Wait until the input handler routine stops // Wait until the input handler routine stops
while (true) Thread.Sleep (500); while (true) Thread.Sleep (500);
@ -53,34 +82,34 @@ class Program {
Environment.Exit (1); Environment.Exit (1);
} }
private static List<MenuItem> CreateMenuItems (nint screen, InputHandler inputHandler) { // private static List<MenuItem> CreateMenuItems (nint screen, InputHandler inputHandler) {
var helpItem = new MenuItem ("Help", "F1", inputHandler); // var helpItem = new MenuItem ("Help", "F1", inputHandler);
var upcomingItem = new MenuItem ("Upcoming", "F2", inputHandler); // var upcomingItem = new MenuItem ("Upcoming", "F2", inputHandler);
var byDayItem = new MenuItem ("By Day", "F3", inputHandler); // var byDayItem = new MenuItem ("By Day", "F3", inputHandler);
var byRoomItem = new MenuItem ("By Room", "F4", inputHandler); // var byRoomItem = new MenuItem ("By Room", "F4", inputHandler);
var bySpeakerItem = new MenuItem ("By Speaker", "F5", inputHandler); // var bySpeakerItem = new MenuItem ("By Speaker", "F5", inputHandler);
var quitItem = new MenuItem ("Quit", "q", inputHandler); // var quitItem = new MenuItem ("Quit", "q", inputHandler);
quitItem.OnItemActivated += (object? sender, EventArgs e) => { // quitItem.OnItemActivated += (object? sender, EventArgs e) => {
NCurses.EndWin (); // NCurses.EndWin ();
Console.WriteLine ("Bye-bye!"); // Console.WriteLine ("Bye-bye!");
Environment.Exit (0); // Environment.Exit (0);
}; // };
helpItem.OnItemActivated += (object? sender, EventArgs e) => { // helpItem.OnItemActivated += (object? sender, EventArgs e) => {
InputBox.InputCompleted callback = (string ee) => { // InputBox.InputCompleted callback = (string ee) => {
NCurses.MoveAddString (3, 3, $"<{ee}>"); // NCurses.MoveAddString (3, 3, $"<{ee}>");
}; // };
new InputBox().RequestInput (screen, inputHandler, callback, "Please enter some text now:"); // new InputBox().RequestInput (screen, inputHandler, callback, "Please enter some text now:");
}; // };
return new List<MenuItem> { // return new List<MenuItem> {
helpItem, // helpItem,
upcomingItem, // upcomingItem,
byDayItem, // byDayItem,
byRoomItem, // byRoomItem,
bySpeakerItem, // bySpeakerItem,
quitItem // quitItem
}; // };
} // }
} }

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

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

@ -1,4 +1,4 @@
using System.Runtime.InteropServices.Marshalling; using SCI.CursesWrapper;
using Mindmagma.Curses; using Mindmagma.Curses;
namespace ANSI_Fahrplan.UiElements; namespace ANSI_Fahrplan.UiElements;
@ -77,7 +77,7 @@ public class InputBox {
NCurses.SetCursor (0); NCurses.SetCursor (0);
NCurses.NoEcho (); NCurses.NoEcho ();
inputHandler.DisableRawEventHandler (screen); //inputHandler.DisableRawEventHandler (screen);
NCurses.MoveWindow (screen, 0, 0); NCurses.MoveWindow (screen, 0, 0);
NCurses.DeleteWindow (inputFieldWin); NCurses.DeleteWindow (inputFieldWin);
@ -93,6 +93,6 @@ public class InputBox {
NCurses.MoveWindow (inputFieldWin, inputFieldWinY, inputFieldWinX); NCurses.MoveWindow (inputFieldWin, inputFieldWinY, inputFieldWinX);
}; };
inputHandler.EnableRawEventHandler (handlerFunction, inputFieldWin); //inputHandler.EnableRawEventHandler (handlerFunction, inputFieldWin);
} }
} }

@ -1,3 +1,4 @@
using SCI.CursesWrapper;
using Mindmagma.Curses; using Mindmagma.Curses;
namespace ANSI_Fahrplan.UiElements; namespace ANSI_Fahrplan.UiElements;

@ -1,3 +1,4 @@
using SCI.CursesWrapper;
using Mindmagma.Curses; using Mindmagma.Curses;
namespace ANSI_Fahrplan.UiElements; namespace ANSI_Fahrplan.UiElements;

Loading…
Cancel
Save