diff --git a/AsciiArt/AsciiArt.cs b/AsciiArt/AsciiArt.cs index e65a78a..84f8315 100644 --- a/AsciiArt/AsciiArt.cs +++ b/AsciiArt/AsciiArt.cs @@ -56,16 +56,16 @@ public class AsciiArt { public static string logo38c3_80x24 { get { return @" - - .;lllllllllc, .;c,:oddoc;. 'coddoc' ,clllllllll:. - kMMMMWKOdc;'xx cWMN' 'lkXMMO .dNMM0l' kMN: :MMMMMX0xl:':K - ,;'..':ok0kc ;NMMN' .cNKl .NXx;. NMMW. ';,...;lxOOo. - l0NMMM0c. .:o0lldl, xMo,. dKKo ;kXWMMNd' - 'WMMWo::oOXX0' :kO0kl;;lkK0k, OMMMMWK, 0MMMOc;lxKNKc - .,;;clodkXMc :MKo. .l0W. cMMMW0o. ;0WN, .',;:lodxKM0 - cOkkxdollc::l: .XOddddxxxxxxkO cNMx ,0MMXo 'kOkxddolc:::l - .lxkkkkxoc,. ':odxkkkxoc' ,lc;lxo:. :dxkkkxdl;. - + + .;lllllllllc, .;c,:oddoc;. 'coddoc' ,clllllllll:. + kMMMMWKOdc;'xx cWMN' 'lkXMMO .dNMM0l' kMN: :MMMMMX0xl:':K + ,;'..':ok0kc ;NMMN' .cNKl .NXx;. NMMW. ';,...;lxOOo. + l0NMMM0c. .:o0lldl, xMo,. dKKo ;kXWMMNd' + 'WMMWo::oOXX0' :kO0kl;;lkK0k, OMMMMWK, 0MMMOc;lxKNKc + .,;;clodkXMc :MKo. .l0W. cMMMW0o. ;0WN, .',;:lodxKM0 + cOkkxdollc::l: .XOddddxxxxxxkO cNMx ,0MMXo 'kOkxddolc:::l + .lxkkkkxoc,. ':odxkkkxoc' ,lc;lxo:. :dxkkkxdl;. + "; }} diff --git a/CursesWrapper.cs b/CursesWrapper.cs index c74b94e..d3d8308 100755 --- a/CursesWrapper.cs +++ b/CursesWrapper.cs @@ -17,22 +17,22 @@ public class Helper { /// nint - Pointer of new window /// Throws exception if window is outside of screen bounds 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; + } /// /// 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 MenuItems { get; set; } = new List (); - - 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 SubMenuItems = new List (); - - public enum MenuType { - Simple, - TopMenu, - SubMenu, - Spacer - } - } -} - diff --git a/CursesWrapper/CursesWrapper.cs b/CursesWrapper/CursesWrapper.cs index b539e5d..3b6104e 100644 --- a/CursesWrapper/CursesWrapper.cs +++ b/CursesWrapper/CursesWrapper.cs @@ -3,39 +3,54 @@ using Mindmagma.Curses; namespace SCI.CursesWrapper; public class CursesWrapper { - /// - /// Initializes NCurses and creates a screen - /// - public static nint InitNCurses () { - var screen = NCurses.InitScreen (); + /// + /// Initializes NCurses and creates a screen + /// + 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; + } - /// - /// 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 width of a window/screen + /// + /// 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; - } + /// + /// 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; + } + + /// + /// Compares a key code to a character to test for a held + /// Control key during the key press event + /// + /// + /// + /// + public static bool KeyPressIsCtrl (int keyCode, int testChar) { + return keyCode == (testChar & 0x1f); + } } \ No newline at end of file diff --git a/CursesWrapper/InnerWindow.cs b/CursesWrapper/InnerWindow.cs new file mode 100644 index 0000000..20a09b6 --- /dev/null +++ b/CursesWrapper/InnerWindow.cs @@ -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 + ) + { } +} \ No newline at end of file diff --git a/CursesWrapper/InputHandler.cs b/CursesWrapper/InputHandler.cs index 80e1b04..7e1aa68 100644 --- a/CursesWrapper/InputHandler.cs +++ b/CursesWrapper/InputHandler.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Mindmagma.Curses; namespace SCI.CursesWrapper; @@ -33,11 +34,6 @@ public class InputHandler { } } - /// - /// Root screen to fall back to when no window is active - /// - private nint rootScreen; - /// /// Task running the endless input handler routine /// @@ -48,14 +44,6 @@ public class InputHandler { /// private CancellationTokenSource? listeningRoutineCts; - /// - /// Class constructor - /// Parent screen this InputHandler is attached to - /// - public InputHandler (nint screen) { - rootScreen = screen; - } - /// /// Start listening to key presses and call registered callbacks /// @@ -96,59 +84,59 @@ public class InputHandler { ); } - /// - /// Prepares the window for proper key press reading - /// - 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); - } - } - - /// - /// Undoes any changes the setup routine configured for the active window - /// - 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); - } - } - /// /// Actual keypress handler being called as Task by StartListening () /// 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] + [] + 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; } } \ No newline at end of file diff --git a/CursesWrapper/Window.cs b/CursesWrapper/Window.cs index ecc13d4..e1699fa 100644 --- a/CursesWrapper/Window.cs +++ b/CursesWrapper/Window.cs @@ -5,261 +5,351 @@ 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, ' ', ' '); - } - } + public const int InnerPadding = 1; + + /// + /// Gets or sets the window position on the screen + /// + 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; + } + } + + /// + /// 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); + Draw (); + _backgroundColorId = value; + } + } + + private bool _borderEnabled; + public bool BorderEnabled { + get { + return _borderEnabled; + } + set { + SetBorder (value); + Draw (); + _borderEnabled = 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 + /// + protected InputHandler? _targetInputHandler; + public InputHandler? TargetInputHandler { get { return _targetInputHandler; }} + + /// + /// 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) { + _Initialize (new Point (x, y), new Size (width, height), parentWindow); + } + + /// + /// Create new window by specifying geometry through Point and Size objects + /// + /// + /// + /// + public Window (Point position, Size windowSize, Window? parentWindow = null) { + _Initialize (position, windowSize, parentWindow); + } + + /// + /// Create new window by specifying X/Y and Width/Height geometry + /// + /// + /// + /// + /// + /// + /// + 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 (); + } + + /// + /// Create new window by specifying geometry through Point and Size objects + /// + /// + /// + /// + /// + public Window (Point position, Size windowSize, InputHandler targetInputHandler, Window? parentWindow = null) { + _Initialize (position, windowSize, parentWindow); + RegisterInputHandler (targetInputHandler); + SetWindowActive (); + } + + /// + /// Actual initialization function for this class + /// + /// + /// + /// + 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 (); + } + + /// + /// Destroys the window and its sub-windows when the Window object is destroyed + /// + ~Window () { + Destroy (); + } + + /// + /// 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) { + _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); + } + + /// + /// Draws this window and all sub windows + /// + public void Draw () { + NCurses.Refresh (); // TODO: Necessary? + + foreach (var window in ChildWindows) { + window.Draw (); + } + + if (ParentWindow is not null) ParentWindow.TouchWin (); + NCurses.WindowRefresh (WindowId); + } + + /// + /// Destroys this window and all children windows + /// + 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 (); + } + } + + /// + /// Register an input handler for this window to attach to OnKeyPress events + /// + /// InputHandler to register + public void RegisterInputHandler (InputHandler inputHandler) { + if (TargetInputHandler is not null) throw new Exception ( + "Another input handler is already registered" + ); + + _targetInputHandler = inputHandler; + TargetInputHandler!.OnKeyPress += KeyPressHandler; + } + + /// + /// Detach from all OnKeyPress events and unset input handler + /// + public void UnregisterInputHandler () { + if (TargetInputHandler is null) return; + + if (TargetInputHandler.ActiveWindow == this) TargetInputHandler.ActiveWindow = null; + TargetInputHandler.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 (this, e); + } + } + + /// + /// Tells the input handler this window is active + /// + public void SetWindowActive () { + if (TargetInputHandler is null) return; + + TargetInputHandler.ActiveWindow = this; + Draw (); + } + + /// + /// 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, ' ', ' '); + } + } + + /// + /// Calcaulates the usable inner width of this window + /// + /// Usable inner width of window in columns + public int GetUsableWidth () { + if (BorderEnabled) { + return WindowSize.Width - 1 - InnerPadding; + } else { + return WindowSize.Width; + } + } + + /// + /// Calcaulates the usable inner height of this window + /// + /// Usable inner height of window in rows + public int GetUsableHeight () { + if (BorderEnabled) { + return WindowSize.Height - 1 - InnerPadding; + } else { + return WindowSize.Height; + } + } } \ No newline at end of file diff --git a/Fahrplan.cs b/Fahrplan.cs index 384d4c1..7bcf0d1 100644 --- a/Fahrplan.cs +++ b/Fahrplan.cs @@ -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 rooms { get; set; } - public List tracks { get; set; } - public List 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 rooms { get; set; } + public List tracks { get; set; } + public List 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> 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> 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 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 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 persons { get; set; } - public List links { get; set; } - public List attachments { get; set; } - public List 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 persons { get; set; } + public List links { get; set; } + public List attachments { get; set; } + public List 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; } } diff --git a/Playground.cs b/Playground.cs index 80eec26..b67fe6c 100644 --- a/Playground.cs +++ b/Playground.cs @@ -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 (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 () { - 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 () { + 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); - - while (inputTask.Status == TaskStatus.Running) { - NCurses.Refresh (); - //NCurses.WindowRefresh (window); - NCurses.Nap (1000 / 30); - } - - //NCurses.AttributeOff (NCurses.ColorPair (1)); - NCurses.EndWin (); - } - - static void InputRoutine () { - int chr = 0; - while (chr != CursesKey.ESC) { - chr = NCurses.GetChar (); - - /*if (chr == 265) { - menu.Render (0); - } else if (chr == 266) { - menu.Render (1); - } else if (chr == 267) { - menu.Render (2); - } else if (chr > 0) { - menu.Render (); - - NCurses.MoveAddString (2, 10, $"You pressed the key {chr} "); - }*/ + var inputTask = Task.Run (() => InputRoutine ()); + while (inputTask.Status == TaskStatus.WaitingToRun); + + while (inputTask.Status == TaskStatus.Running) { + NCurses.Refresh (); + //NCurses.WindowRefresh (window); + NCurses.Nap (1000 / 30); + } + + //NCurses.AttributeOff (NCurses.ColorPair (1)); + NCurses.EndWin (); + } + + static void InputRoutine () { + int chr = 0; + while (chr != CursesKey.ESC) { + chr = NCurses.GetChar (); + + /*if (chr == 265) { + menu.Render (0); + } else if (chr == 266) { + menu.Render (1); + } else if (chr == 267) { + menu.Render (2); + } else if (chr > 0) { + menu.Render (); + + NCurses.MoveAddString (2, 10, $"You pressed the key {chr} "); + }*/ if (chr > 0) NCurses.MoveAddString (2, 10, $"You pressed the key {chr} "); - } + } - /*NCurses.WindowBorder (window, ' ', ' ', ' ',' ',' ',' ',' ',' '); - NCurses.WindowRefresh (window); - NCurses.DeleteWindow (window);*/ + /*NCurses.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; + } } \ No newline at end of file diff --git a/Program.cs b/Program.cs index 90fb10b..b97350c 100755 --- a/Program.cs +++ b/Program.cs @@ -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 (); // -- 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); - } - - if (e.KeyCode != 'd') return; + // Wait until intro screen is closed + while (introScreen.WindowId > -1) Thread.Sleep (50); // TODO; Unjank this - 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 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 { - // helpItem, - // upcomingItem, - // byDayItem, - // byRoomItem, - // bySpeakerItem, - // quitItem - // }; - // } + private static List 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 { + helpItem, + upcomingItem, + byDayItem, + byRoomItem, + bySpeakerItem, + quitItem + }; + } } diff --git a/Screens/HeaderFooter.cs b/Screens/HeaderFooter.cs deleted file mode 100644 index d848030..0000000 --- a/Screens/HeaderFooter.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace ANSI_Fahrplan.Screens; - -/// -/// NCurses Header Window -/// -public class Header { - - - public Header () { - - } -} \ No newline at end of file diff --git a/Screens/HelpScreen.cs b/Screens/HelpScreen.cs new file mode 100644 index 0000000..e69de29 diff --git a/Screens/IntroScreen.cs b/Screens/IntroScreen.cs index 834f672..fdb00be 100644 --- a/Screens/IntroScreen.cs +++ b/Screens/IntroScreen.cs @@ -1,28 +1,26 @@ -using Mindmagma.Curses; using SCI.CursesWrapper; namespace ANSI_Fahrplan.Screens; public class IntroScreen : Window { - private nint infoTextBox; - /// /// Pass class constructor through to inherited constructor /// /// Parent window of this window object - 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); } } \ No newline at end of file diff --git a/Screens/Screen.cs b/Screens/Screen.cs index 5bfe9a1..fe92673 100644 --- a/Screens/Screen.cs +++ b/Screens/Screen.cs @@ -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); diff --git a/Screens/ScrollableScreen.cs b/Screens/ScrollableScreen.cs index 23821ef..0350210 100644 --- a/Screens/ScrollableScreen.cs +++ b/Screens/ScrollableScreen.cs @@ -1,8 +1,8 @@ namespace ANSI_Fahrplan.Screens; public abstract class ScrollableScreen : Screen { - - public ScrollableScreen (nint rootWindow) : base (rootWindow) {} + + public ScrollableScreen (nint rootWindow) : base (rootWindow) {} - + } \ No newline at end of file diff --git a/UiElements/InputBox.cs b/UiElements/InputBox.cs index 281b811..c0f48e1 100644 --- a/UiElements/InputBox.cs +++ b/UiElements/InputBox.cs @@ -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); diff --git a/UiElements/MenuItem.cs b/UiElements/MenuItem.cs index 2e7af1b..3136c9d 100644 --- a/UiElements/MenuItem.cs +++ b/UiElements/MenuItem.cs @@ -1,7 +1,6 @@ -using SCI.CursesWrapper; using Mindmagma.Curses; -namespace ANSI_Fahrplan.UiElements; +namespace SCI.CursesWrapper.UiElements; public class MenuItem { private List _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? childItems = null) { + /// + /// Class constructor + /// + /// + /// + /// + public MenuItem (string label, string? key = null, List? childItems = null) { _childItems = childItems ?? new List (); _key = key; - _inputHandler = inputHandler; _label = label; } - public void KeyHandler (object sender, NCursesKeyPressEventArgs e) { + /// + /// Sets this menu items parent TopMenu window + /// + /// TopMenu window this menu item belongs to + public void AssignParentTopMenuWindow (TopMenu parent) { + if (_parentTopMenuWindow is not null) return; + _parentTopMenuWindow = parent; + } + + /// + /// Called when key on parent TopMenu window is pressed + /// + /// Event sender, should be a Window object + /// Key Press Event arguments + 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; } } \ No newline at end of file diff --git a/UiElements/TopMenu.cs b/UiElements/TopMenu.cs index 5e72440..7bd41ab 100644 --- a/UiElements/TopMenu.cs +++ b/UiElements/TopMenu.cs @@ -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 _menuItems = new List (); - public List MenuItems { + public ReadOnlyCollection MenuItems { get { - return _menuItems; - } - set { - UnregisterItemEventHandlers (); - _menuItems = value; - RegisterItemEventHandlers (); - DrawMenu (); + return _menuItems.AsReadOnly (); } } private MenuItem? activeMenuItem; - public TopMenu (nint screen, List menuItems) : base (screen) { - NCurses.GetMaxYX (screen, out _, out int width); - innerWindow = NCurses.NewWindow (1, width, 0, 0); - + /// + /// Class constructor + /// + /// + /// + /// + public TopMenu (nint screen, InputHandler targetInputHandler, List 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 (); } /// /// Register key event handlers for each menu item /// 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 /// 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 /// 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 (); } } \ No newline at end of file diff --git a/ansifahrplan.csproj b/ansifahrplan.csproj index 24e7173..0271387 100755 --- a/ansifahrplan.csproj +++ b/ansifahrplan.csproj @@ -1,16 +1,16 @@  - Exe - net8.0 - enable - enable - true - true + Exe + net8.0 + enable + enable + true + true - - + +