@ -5,261 +5,351 @@ 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 , ' ' , ' ' ) ;
}
}
public const int InnerPadding = 1 ;
/// <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 ) {
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 ;
}
}
/// <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 ) ;
Draw ( ) ;
_backgroundColorId = value ;
}
}
private bool _borderEnabled ;
public bool BorderEnabled {
get {
return _borderEnabled ;
}
set {
SetBorder ( value ) ;
Draw ( ) ;
_borderEnabled = 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>
protected InputHandler ? _targetInputHandler ;
public InputHandler ? TargetInputHandler { get { return _targetInputHandler ; } }
/// <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 ) {
_Initialize ( new Point ( x , y ) , new Size ( width , height ) , parentWindow ) ;
}
/// <summary>
/// Create new window by specifying geometry through Point and Size objects
/// </summary>
/// <param name="position"></param>
/// <param name="windowSize"></param>
/// <param name="parentWindow"></param>
public Window ( Point position , Size windowSize , Window ? parentWindow = null ) {
_Initialize ( position , windowSize , parentWindow ) ;
}
/// <summary>
/// Create new window by specifying X/Y and Width/Height geometry
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="targetInputHandler"></param>
/// <param name="parentWindow"></param>
public Window ( int x , int y , int width , int height , InputHandler targetInputHandler , Window ? parentWindow = null ) {
_Initialize ( new Point ( x , y ) , new Size ( width , height ) , parentWindow ) ;
RegisterInputHandler ( targetInputHandler ) ;
SetWindowActive ( ) ;
}
/// <summary>
/// Create new window by specifying geometry through Point and Size objects
/// </summary>
/// <param name="position"></param>
/// <param name="windowSize"></param>
/// <param name="targetInputHandler"></param>
/// <param name="parentWindow"></param>
public Window ( Point position , Size windowSize , InputHandler targetInputHandler , Window ? parentWindow = null ) {
_Initialize ( position , windowSize , parentWindow ) ;
RegisterInputHandler ( targetInputHandler ) ;
SetWindowActive ( ) ;
}
/// <summary>
/// Actual initialization function for this class
/// </summary>
/// <param name="position"></param>
/// <param name="windowSize"></param>
/// <param name="parentWindow"></param>
private void _Initialize ( Point position , Size windowSize , Window ? parentWindow = null ) {
if ( parentWindow is not null ) {
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 ( ) ;
}
/// <summary>
/// Destroys the window and its sub-windows when the Window object is destroyed
/// </summary>
~ Window ( ) {
Destroy ( ) ;
}
/// <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 ) {
_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>
/// Draws this window and all sub windows
/// </summary>
public void Draw ( ) {
NCurses . Refresh ( ) ; // TODO: Necessary?
foreach ( var window in ChildWindows ) {
window . Draw ( ) ;
}
if ( ParentWindow is not null ) ParentWindow . TouchWin ( ) ;
NCurses . WindowRefresh ( WindowId ) ;
}
/// <summary>
/// Destroys this window and all children windows
/// </summary>
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 ( ) ;
}
}
/// <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 ;
}
}
}