You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

408 lines
11 KiB

using System.Collections.ObjectModel;
using System.Drawing;
using Mindmagma.Curses;
namespace SCI.CursesWrapper;
public class Window {
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>
/// Implicit conversion of a Window object to nint representing
/// the window id for use directly with NCurses methods
/// </summary>
/// <param name="w">Window object</param>
public static implicit operator nint (Window w) => w.WindowId;
/// <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) {
position.X = parentWindow.GetInnerOriginX () + position.X;
position.Y = parentWindow.GetInnerOriginY () + position.Y;
// If windowSize.Width is 0 or smaller, make the width relative
// to the total width of the window, 0 making it as large as possible
windowSize.Width = (windowSize.Width <= 0)?
parentWindow.GetUsableWidth () - windowSize.Width:
windowSize.Width;
// If windowSize.Height is 0 or smaller, make the height relative
// to the total height of the window, 0 making it as large as possible
windowSize.Height = (windowSize.Height <= 0)?
parentWindow.GetUsableHeight () - windowSize.Height:
windowSize.Height;
_windowId = NCurses.DeriveWindow (
parentWindow.WindowId,
windowSize.Height, windowSize.Width,
position.Y, position.X
);
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);
Clear ();
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>
/// Clears the window with the currently set background color
/// </summary>
public void Clear () {
NCurses.ClearWindow (this);
NCurses.MoveWindowAddString (this, GetInnerOriginY (), GetInnerOriginY (), "");
Draw ();
}
/// <summary>
/// Destroys this window and all children windows
/// <paramref name="skipParentRedrawing">Skips redrawing the windows parent if true</paramref>
/// </summary>
public void Destroy (bool skipParentRedrawing = false) {
// Catch double destroy calls
if (WindowId == -1) throw new Exception ("Destroy called twice on object");
foreach (var window in _childWindows.ToList ()) {
window.Destroy (true);
}
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);
if (!skipParentRedrawing) 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) {
// Total window with - (1col Border Left and Right) - (InnerPadding Left and Right)
return WindowSize.Width - 2 - (InnerPadding * 2);
} 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) {
// Total window with - (1col Border Top and Bottom) - (InnerPadding Top and Bottom)
return WindowSize.Height - 2 - (InnerPadding * 2);
} else {
return WindowSize.Height;
}
}
/// <summary>
/// Calculates the first usable X coordinate inside the window
/// </summary>
/// <returns>Inner Origin X coordinate of window</returns>
public int GetInnerOriginX () {
if (BorderEnabled) {
return 1 + InnerPadding;
} else {
return 0;
}
}
/// <summary>
/// Calculates the first usable Y coordinate inside the window
/// </summary>
/// <returns>Inner Origin Y coordinate of window</returns>
public int GetInnerOriginY () {
// Window borders and padding are symmetrical for now, reuse X function
return GetInnerOriginX ();
}
}