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.
250 lines
7.8 KiB
250 lines
7.8 KiB
|
|
using System.Collections.ObjectModel;
|
|
using System.Drawing;
|
|
using Mindmagma.Curses;
|
|
|
|
namespace SCI.CursesWrapper;
|
|
|
|
public class ScrollWindow : Window {
|
|
private Point _scrollPosition;
|
|
public Point ScrollPosition { get { return _scrollPosition; }}
|
|
|
|
private Size _scrollAreaSize;
|
|
public Size ScrollAreaSize { get { return _scrollAreaSize; }}
|
|
|
|
private List<string> _innerContent = new List<string> ();
|
|
public ReadOnlyCollection<string> InnerContent { get { return _innerContent.AsReadOnly (); }}
|
|
|
|
public ScrollWindowOverflowAction OverflowAction = ScrollWindowOverflowAction.ResizeScrollArea;
|
|
|
|
/// <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 ScrollWindow (int x, int y, int width, int height, InputHandler targetInputHandler, Window? parentWindow = null) :
|
|
base (x, y, width, height, targetInputHandler, parentWindow)
|
|
{
|
|
_InitializeScrollingWindow ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create new scrolling 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 ScrollWindow (Point position, Size windowSize, InputHandler targetInputHandler, Window? parentWindow = null) :
|
|
base (position, windowSize, targetInputHandler, parentWindow)
|
|
{
|
|
_InitializeScrollingWindow ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run initialization code upon object creation
|
|
/// </summary>
|
|
private void _InitializeScrollingWindow () {
|
|
_scrollAreaSize = new Size (
|
|
WindowSize.Width,
|
|
WindowSize.Height
|
|
);
|
|
|
|
OnKeyPress += ScrollKeyHandler;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls the window up or down on up key or down key
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void ScrollKeyHandler (object sender, NCursesKeyPressEventArgs e) {
|
|
if (e.SourceWindow is null || e.SourceWindow != this) return;
|
|
|
|
if (e.KeyCode == CursesKey.UP) {
|
|
ScrollTo (ScrollPosition.X, ScrollPosition.Y - 1);
|
|
} else if (e.KeyCode == CursesKey.DOWN) {
|
|
ScrollTo (ScrollPosition.X, ScrollPosition.Y + 1);
|
|
} else if (e.KeyCode == CursesKey.LEFT) {
|
|
ScrollTo (ScrollPosition.X - 1, ScrollPosition.Y);
|
|
} else if (e.KeyCode == CursesKey.RIGHT) {
|
|
ScrollTo (ScrollPosition.X + 1, ScrollPosition.Y);
|
|
} else if (e.KeyCode == 339) { // Page Up
|
|
ScrollTo (ScrollPosition.X, ScrollPosition.Y - WindowSize.Height);
|
|
} else if (e.KeyCode == 338) { // Page Down
|
|
ScrollTo (ScrollPosition.X, ScrollPosition.Y + WindowSize.Height);
|
|
} else if (e.KeyCode == CursesKey.HOME) {
|
|
ScrollTo (0, ScrollPosition.Y);
|
|
} else if (e.KeyCode == CursesKey.END) {
|
|
ScrollTo (ScrollAreaSize.Width, ScrollPosition.Y);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls the pad to the specified coordinates
|
|
/// </summary>
|
|
/// <param name="scrollX"></param>
|
|
/// <param name="scrollY"></param>
|
|
public void ScrollTo (int scrollX, int scrollY) {
|
|
_scrollPosition = new Point (scrollX, scrollY);
|
|
RenderCurrentViewport ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds text content to this window
|
|
/// </summary>
|
|
/// <param name="contentStr">Block of text to add to this window</param>
|
|
public void AddContent (string contentStr) {
|
|
var lines = contentStr.Split ('\n', StringSplitOptions.RemoveEmptyEntries);
|
|
AddContent (lines);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds text content to this window
|
|
/// </summary>
|
|
/// <param name="lines">Lines of text to add to this window</param>
|
|
public void AddContent (string[] lines) {
|
|
// TODO: Iterate over every line and turn \n into an actual new line
|
|
// TODO: When iterating over every line, do overflow checks
|
|
|
|
foreach (var _line in lines) {
|
|
// Necessary to make the current line editable
|
|
var line = _line;
|
|
|
|
// Remove trailing new line character
|
|
if (line.EndsWith ('\n'))
|
|
line = line.Substring (0, line.Length - 1);
|
|
|
|
// Turn every new line character into an actual new content line
|
|
var sublines = line.Split ('\n');
|
|
|
|
// If there are new line splits in the current line, add those
|
|
// by calling this function recusively on those lines again
|
|
if (sublines.Length > 1) {
|
|
AddContent (sublines);
|
|
continue;
|
|
}
|
|
|
|
// Do overflow action if text is overflowing
|
|
if (line.Length > ScrollAreaSize.Width) {
|
|
switch (OverflowAction) {
|
|
case ScrollWindowOverflowAction.CutOff:
|
|
line = line.Substring (0, WindowSize.Width);
|
|
break;
|
|
case ScrollWindowOverflowAction.ResizeScrollArea:
|
|
_scrollAreaSize.Width = line.Length;
|
|
break;
|
|
case ScrollWindowOverflowAction.ThrowException:
|
|
throw new OverflowException ("Line too long for scrollable window area");
|
|
}
|
|
}
|
|
|
|
// Finally, add string to content string list
|
|
_innerContent.Add (line);
|
|
}
|
|
|
|
_scrollAreaSize.Height = _innerContent.Count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the currently visible part of the inner content
|
|
/// </summary>
|
|
public void RenderCurrentViewport () {
|
|
var visibleArea = new Rectangle (
|
|
ScrollPosition.X,
|
|
ScrollPosition.Y,
|
|
WindowSize.Width,
|
|
WindowSize.Height
|
|
);
|
|
|
|
// Check top/left bounds of scroll area
|
|
if (visibleArea.X < 0) {
|
|
visibleArea.X = 0;
|
|
_scrollPosition.X = 0;
|
|
}
|
|
if (visibleArea.Y < 0) {
|
|
visibleArea.Y = 0;
|
|
_scrollPosition.Y = 0;
|
|
}
|
|
|
|
// Check bottom/right bounds of scroll area
|
|
if (visibleArea.Right > ScrollAreaSize.Width) {
|
|
visibleArea.X = ScrollAreaSize.Width - visibleArea.Width;
|
|
_scrollPosition.X = visibleArea.X;
|
|
}
|
|
|
|
if (visibleArea.Bottom > ScrollAreaSize.Height) {
|
|
visibleArea.Y = ScrollAreaSize.Height - visibleArea.Height;
|
|
_scrollPosition.Y = visibleArea.Y;
|
|
}
|
|
|
|
// Do not scroll if the virtual scroll area is smaller or
|
|
// the same size of the actual window
|
|
if (ScrollAreaSize.Width <= visibleArea.Width) {
|
|
visibleArea.X = 0;
|
|
_scrollPosition.X = 0;
|
|
}
|
|
if (ScrollAreaSize.Height <= visibleArea.Height) {
|
|
visibleArea.Y = 0;
|
|
_scrollPosition.Y = 0;
|
|
}
|
|
|
|
// Clear inner window
|
|
Clear ();
|
|
|
|
var textToAdd = "";
|
|
|
|
for (int linePos = visibleArea.Y; linePos < visibleArea.Bottom; linePos++) {
|
|
if (linePos > _innerContent.Count - 1) break;
|
|
var line = _innerContent [linePos];
|
|
|
|
// Cut out only the visible part of a line
|
|
if (line.Length < visibleArea.X) {
|
|
line = "";
|
|
} else if (line.Length >= visibleArea.Right) {
|
|
line = line.Substring (visibleArea.X, visibleArea.Width);
|
|
} else {
|
|
line = line.Substring (visibleArea.X);
|
|
}
|
|
|
|
// Last line on the viewport
|
|
if (linePos >= visibleArea.Bottom - 1) {
|
|
// On the last line, remove the last character if the line is as long as the window.
|
|
// NCurses throws an error on the last character due to historical reasons,
|
|
// See: https://stackoverflow.com/q/10877469
|
|
// I accept the data loss at this point because the only "proper" solution
|
|
// by pushing characters around does not work as the C# NCurses wrapper
|
|
// used in this project lacks the insch function and just catching the Exception
|
|
// makes the whole screen flicker with each scroll
|
|
if (line.Length >= visibleArea.Width) {
|
|
line = line.Substring (0, line.Length - 1);
|
|
}
|
|
} else if (line.Length < visibleArea.Width) {
|
|
// Add new line to the end of a cut-off line that is smaller than the window
|
|
line = $"{line}\n";
|
|
}
|
|
|
|
textToAdd += line;
|
|
}
|
|
|
|
try {
|
|
NCurses.WindowAddString (this, textToAdd);
|
|
} catch (Exception) {}
|
|
Draw ();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines how to deal with lines to long for a scrolling window
|
|
/// </summary>
|
|
public enum ScrollWindowOverflowAction {
|
|
OverflowNewLine, // Let the content overflow onto the next line
|
|
CutOff, // Cut the overflowing text off at the border
|
|
ResizeScrollArea, // Resizes the virtual scroll area (default)
|
|
ThrowException // Throw a warning
|
|
}
|