Added scrolling window

main
resneptacle 3 weeks ago
parent d0853e23bc
commit be2c4fab03

@ -72,7 +72,7 @@ public class AsciiArt {
public static string lorem_ipsum { get {
return
@"
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer euismod in sapien id rhoncus. Suspendisse potenti. Nam bibendum risus lobortis mollis commodo. Donec ac ante nec augue vestibulum consequat. In condimentum id nisl sed placerat. Suspendisse rhoncus lectus a risus sollicitudin accumsan. Quisque semper dui a erat aliquet, ac venenatis felis blandit. In id eros quis tellus condimentum luctus et sit amet enim. Aliquam malesuada erat augue, in euismod libero rhoncus quis. Nam augue sapien, pellentesque quis congue ac, semper nec nisl. Donec accumsan vitae justo ac volutpat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus in dictum ipsum. Nunc venenatis egestas metus id maximus. Aliquam erat volutpat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer euismod in sapien id rhoncus. Suspendisse potenti. Nam bibendum risus lobortis mollis commodo. Donec ac ante nec augue vestibulum consequat. In condimentum id nisl sed placerat. Suspendisse rhoncus lectus a risus sollicitudin accumsan. Quisque semper dui a erat aliquet, ac venenatis felis blandit. In id eros quis tellus condimentum luctus et sit amet enim. Aliquam malesuada erat augue, in euismod libero rhoncus quis. Nam augue sapien, pellentesque quis congue ac, semper nec nisl. Donec accumsan vitae justo ac volutpat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus in dictum ipsum. Nunc venenatis egestas metus id maximus. Aliquam erat volutpat.
Fusce vitae tortor a tortor hendrerit condimentum quis non libero. Proin eget dignissim neque. Aliquam ac justo tempus, semper diam eget, finibus eros. Nullam elementum odio ante, eu volutpat sapien aliquet quis. Nulla eget volutpat lorem. Sed feugiat est vel tellus sodales, ac mollis libero faucibus. Vestibulum auctor dapibus quam, eu efficitur dolor feugiat id. Vestibulum tincidunt maximus arcu. Integer a urna posuere sapien efficitur tincidunt ut nec nibh. Etiam eu ultrices metus, in pretium leo. Duis egestas arcu quis tristique pretium. Maecenas dapibus enim vel quam malesuada elementum. Fusce vulputate mollis fringilla. Nullam fermentum magna velit, ac pharetra ante viverra ac.

@ -15,6 +15,12 @@ public class ColorSchemes {
// TextInputField
NCurses.InitPair (4, CursesColor.BLACK, CursesColor.WHITE);
// CustomColorTest
NCurses.InitPair (5, CursesColor.WHITE, CursesColor.RED);
// CustomColorTest2
NCurses.InitPair (6, CursesColor.BLUE, CursesColor.YELLOW);
}
public static uint Default () {
@ -34,6 +40,10 @@ public class ColorSchemes {
}
public static uint CustomColorTest () {
return NCurses.ColorPair (21);
return NCurses.ColorPair (5);
}
public static uint CustomColorTest2 () {
return NCurses.ColorPair (6);
}
}

@ -13,13 +13,39 @@ public class ContentWindow : Window {
{ }
/// <summary>
/// Creates a new nested child window without inner padding
/// Removes current children and creates a new
/// nested child window without inner padding
/// </summary>
/// <returns>New child window</returns>
public Window CreateInnerWindow () {
Clean ();
if (TargetInputHandler is not null) {
return new Window (-1, -1, -2, -2, TargetInputHandler, this) {
BorderEnabled = false
};
} else {
return new Window (-1, -1, -2, -2, this) {
BorderEnabled = false
};
}
}
/// <summary>
/// Removes current children and creates a new
/// nested scrollable child window without inner padding
/// </summary>
/// <returns>New scrollable child window</returns>
/// <exception cref="NullReferenceException">Throws when TargetInputHandler is null</exception>
public ScrollWindow CreateInnerScrollWindow () {
if (TargetInputHandler is null) throw new NullReferenceException (
"Input handler of Content Window cannot be null when creating child ScrollWindow"
);
return new ScrollWindow (-1, -1, -2, -2, TargetInputHandler, this) {
BorderEnabled = false
};
}
/// <summary>
/// Removes all children elements and redraws the window

@ -1,35 +1,58 @@
using System.Collections.ObjectModel;
using System.Drawing;
using Mindmagma.Curses;
namespace SCI.CursesWrapper;
public class ScrollWindow : Window {
private nint _innerPadId;
public nint InnerPadId { get { return _innerPadId; }}
private Point _scrollPosition;
public Point ScrollPosition { get { return _scrollPosition; }}
private Size _padSize;
public Size PadSize { get { return _padSize; }}
private Size _scrollAreaSize;
public Size ScrollAreaSize { get { return _scrollAreaSize; }}
public ScrollWindow (Window parentWindow, InputHandler inputHandler, int innerHeight = -1) :
base (0, 0, 0, 0, inputHandler, parentWindow)
{
var padWidth = GetUsableWidth ();
var padHeight = innerHeight;
private List<string> _innerContent = new List<string> ();
public ReadOnlyCollection<string> InnerContent { get { return _innerContent.AsReadOnly (); }}
if (padHeight == -1) {
padHeight = parentWindow.GetUsableHeight ();
}
public ScrollWindowOverflowAction OverflowAction = ScrollWindowOverflowAction.ResizeScrollArea;
_innerPadId = NCurses.NewPad (padHeight, padWidth);
/// <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 ();
}
_padSize = new Size (padWidth, padHeight);
/// <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 ();
}
NCurses.WindowBackground (InnerPadId, ColorSchemes.TextInputField ());
ShowPad ();
/// <summary>
/// Run initialization code upon object creation
/// </summary>
private void _InitializeScrollingWindow () {
_scrollAreaSize = new Size (
WindowSize.Width,
WindowSize.Height
);
OnKeyPress += ScrollKeyHandler;
}
@ -46,6 +69,18 @@ public class ScrollWindow : Window {
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);
}
}
@ -55,55 +90,160 @@ public class ScrollWindow : Window {
/// <param name="scrollX"></param>
/// <param name="scrollY"></param>
public void ScrollTo (int scrollX, int scrollY) {
if (ParentWindow is null) return;
_scrollPosition = new Point (scrollX, scrollY);
ShowPad ();
RenderCurrentViewport ();
}
/// <summary>
/// Shows the pad the current scroll location
/// Adds text content to this window
/// </summary>
public void ShowPad () {
if (ParentWindow is null) return;
/// <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);
}
var scrollToX = ScrollPosition.X;
var scrollToY = ScrollPosition.Y;
/// <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;
}
var viewableWidth = GetUsableWidth ();
var viewableHeight = GetUsableHeight ();
// 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");
}
}
// Check for unnecessary scrolling due to small Pad size
if (PadSize.Width <= viewableWidth) {
scrollToX = 0;
// Finally, add string to content string list
_innerContent.Add (line);
}
if (PadSize.Height <= viewableHeight) {
scrollToY = 0;
_scrollAreaSize.Height = _innerContent.Count;
}
// Check values for upper bounds
if (scrollToX > PadSize.Width - viewableWidth) scrollToX = PadSize.Width - viewableWidth;
if (scrollToY > PadSize.Height - viewableHeight) scrollToY = PadSize.Height - viewableHeight;
/// <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 values for lower bounds
if (scrollToX < 0) scrollToX = 0;
if (scrollToY < 0) scrollToY = 0;
// 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;
}
// Store potentially updated scroll coordinates
_scrollPosition = new Point (scrollToX, scrollToY);
// Check bottom/right bounds of scroll area
if (visibleArea.Right > ScrollAreaSize.Width) {
visibleArea.X = ScrollAreaSize.Width - visibleArea.Width;
_scrollPosition.X = visibleArea.X;
}
var fromY = Position.Y;
var fromX = Position.X;
var toY = viewableHeight;
var toX = viewableWidth - 2; // TODO: Why the two? How is the geometry this wong?!
if (visibleArea.Bottom > ScrollAreaSize.Height) {
visibleArea.Y = ScrollAreaSize.Height - visibleArea.Height;
_scrollPosition.Y = visibleArea.Y;
}
NCurses.PadRefresh (
_innerPadId,
scrollToY, scrollToX,
fromY,
fromX,
toY,
toX
);
// 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
}

@ -260,6 +260,7 @@ public class Window {
/// </summary>
public void Clear () {
NCurses.ClearWindow (this);
NCurses.MoveWindowAddString (this, GetInnerOriginY (), GetInnerOriginY (), "");
Draw ();
}

@ -2,8 +2,6 @@
using SCI.CursesWrapper.UiElements;
using ANSI_Fahrplan.Screens;
using Mindmagma.Curses;
using System.Reflection;
using Microsoft.Win32.SafeHandles;
namespace ANSI_Fahrplan;
@ -77,21 +75,23 @@ class Program {
private static List<MenuItem> CreateMenuItems (ContentWindow contentWindow) {
var helpItem = new MenuItem ("Help", "F1");
var upcomingItem = new MenuItem ("Upcoming", "b"); //F2
var byDayItem = new MenuItem ("By Day", "n"); // F3
var upcomingItem = new MenuItem ("Upcoming", "F2");
var byDayItem = new MenuItem ("By Day", "F3");
var byRoomItem = new MenuItem ("By Room", "F4");
var bySpeakerItem = new MenuItem ("By Speaker", "F5");
var quitItem = new MenuItem ("Quit (C-q)");
helpItem.OnItemActivated += (object sender, MenuItemActivatedEventArgs e) => {
contentWindow.Clean ();
if (contentWindow.TargetInputHandler is null) return;
var scrollWindow = new ScrollWindow (contentWindow, contentWindow.TargetInputHandler, 30);
for (int i = 0; i < scrollWindow.PadSize.Height - 1; i++)
NCurses.WindowAddString (scrollWindow.InnerPadId, $"Line {i}\n");
NCurses.WindowAddString (scrollWindow.InnerPadId, $"Line {scrollWindow.PadSize.Height}");
scrollWindow.ShowPad ();
var scrollWindow = contentWindow.CreateInnerScrollWindow ();
for (int i = 0; i <= 100; i++)
scrollWindow.AddContent ($"Line {i} {AsciiArt.lorem_ipsum}\n");
scrollWindow.RenderCurrentViewport ();
};
upcomingItem.OnItemActivated += (object sender, MenuItemActivatedEventArgs e) => {
var scrollWindow = contentWindow.CreateInnerScrollWindow ();
scrollWindow.AddContent ("-- Upcoming --");
scrollWindow.RenderCurrentViewport ();
};
return new List<MenuItem> {

Loading…
Cancel
Save