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