yet more changes

main
resneptacle 2 months ago
parent a6377d1a57
commit d0853e23bc

@ -0,0 +1,39 @@
using System.Diagnostics;
namespace SCI.AsciiArt;
public class Generator {
public static async Task<string> FromImage (string imagePath, int width) {
// Ensure image exists
if (!File.Exists (imagePath)) {
throw new FileNotFoundException ($"The specified file '{imagePath}' does not exist");
}
var process = new Process ();
process.StartInfo = new ProcessStartInfo () {
FileName = "jp2a",
Arguments = $"--width={width} \"{imagePath}\"",
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
UseShellExecute = false
};
process.Start ();
// Wait up to one second for the task to finish
try {
var cancellationTokenSource = new CancellationTokenSource (1000);
await process.WaitForExitAsync (cancellationTokenSource.Token);
} catch (TaskCanceledException) {
throw new TimeoutException ("jp2a did not finish after 1000ms, process was killed");
}
if (process.ExitCode != 0) {
throw new Exception ($"jp2a returned with exit code {process.ExitCode}.\n{process.StandardError.ReadToEnd ()}");
}
return await process.StandardOutput.ReadToEndAsync ();
}
}

@ -32,4 +32,8 @@ public class ColorSchemes {
public static uint TextInputField () { public static uint TextInputField () {
return NCurses.ColorPair (4); return NCurses.ColorPair (4);
} }
public static uint CustomColorTest () {
return NCurses.ColorPair (21);
}
} }

@ -0,0 +1,37 @@
using Mindmagma.Curses;
namespace SCI.CursesWrapper;
public class ContentWindow : Window {
public ContentWindow (nint rootScreen, InputHandler inputHandler) :
base (
0, 1,
CursesWrapper.GetWidth (rootScreen),
CursesWrapper.GetHeight (rootScreen) - 2,
inputHandler
)
{ }
/// <summary>
/// Creates a new nested child window without inner padding
/// </summary>
public Window CreateInnerWindow () {
return new Window (-1, -1, -2, -2, this) {
BorderEnabled = false
};
}
/// <summary>
/// Removes all children elements and redraws the window
/// </summary>
public void Clean () {
foreach (var window in ChildWindows.ToList ()) {
window.Destroy (true);
}
// Clear window and redraw
NCurses.ClearWindow (WindowId);
SetBorder (true);
Draw ();
}
}

@ -1,12 +0,0 @@
namespace SCI.CursesWrapper;
public class InnerWindow : Window {
public InnerWindow (nint rootScreen, InputHandler inputHandler) :
base (
0, 1,
CursesWrapper.GetWidth (rootScreen),
CursesWrapper.GetHeight (rootScreen) - 2,
inputHandler
)
{ }
}

@ -0,0 +1,109 @@
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; }}
public ScrollWindow (Window parentWindow, InputHandler inputHandler, int innerHeight = -1) :
base (0, 0, 0, 0, inputHandler, parentWindow)
{
var padWidth = GetUsableWidth ();
var padHeight = innerHeight;
if (padHeight == -1) {
padHeight = parentWindow.GetUsableHeight ();
}
_innerPadId = NCurses.NewPad (padHeight, padWidth);
_padSize = new Size (padWidth, padHeight);
NCurses.WindowBackground (InnerPadId, ColorSchemes.TextInputField ());
ShowPad ();
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);
}
}
/// <summary>
/// Scrolls the pad to the specified coordinates
/// </summary>
/// <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 ();
}
/// <summary>
/// Shows the pad the current scroll location
/// </summary>
public void ShowPad () {
if (ParentWindow is null) return;
var scrollToX = ScrollPosition.X;
var scrollToY = ScrollPosition.Y;
var viewableWidth = GetUsableWidth ();
var viewableHeight = GetUsableHeight ();
// Check for unnecessary scrolling due to small Pad size
if (PadSize.Width <= viewableWidth) {
scrollToX = 0;
}
if (PadSize.Height <= viewableHeight) {
scrollToY = 0;
}
// Check values for upper bounds
if (scrollToX > PadSize.Width - viewableWidth) scrollToX = PadSize.Width - viewableWidth;
if (scrollToY > PadSize.Height - viewableHeight) scrollToY = PadSize.Height - viewableHeight;
// Check values for lower bounds
if (scrollToX < 0) scrollToX = 0;
if (scrollToY < 0) scrollToY = 0;
// Store potentially updated scroll coordinates
_scrollPosition = new Point (scrollToX, scrollToY);
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?!
NCurses.PadRefresh (
_innerPadId,
scrollToY, scrollToX,
fromY,
fromX,
toY,
toX
);
}
}

@ -105,6 +105,13 @@ public class Window {
/// </summary> /// </summary>
public event InputHandler.KeypressEventHandler? OnKeyPress; 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> /// <summary>
/// Create new window by specifying X/Y and Width/Height geometry /// Create new window by specifying X/Y and Width/Height geometry
/// </summary> /// </summary>
@ -163,13 +170,25 @@ public class Window {
/// <param name="parentWindow"></param> /// <param name="parentWindow"></param>
private void _Initialize (Point position, Size windowSize, Window? parentWindow = null) { private void _Initialize (Point position, Size windowSize, Window? parentWindow = null) {
if (parentWindow is not null) { if (parentWindow is not null) {
int addedPadding = parentWindow.BorderEnabled? position.X = parentWindow.GetInnerOriginX () + position.X;
InnerPadding + 1 : 0; 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 ( _windowId = NCurses.DeriveWindow (
parentWindow.WindowId, parentWindow.WindowId,
windowSize.Height, windowSize.Width, windowSize.Height, windowSize.Width,
position.Y + addedPadding, position.X + addedPadding position.Y, position.X
); );
parentWindow.AddChildWindow (this); parentWindow.AddChildWindow (this);
@ -186,6 +205,7 @@ public class Window {
NCurses.Keypad (WindowId, true); NCurses.Keypad (WindowId, true);
Clear ();
Draw (); Draw ();
} }
@ -235,15 +255,24 @@ public class Window {
NCurses.WindowRefresh (WindowId); NCurses.WindowRefresh (WindowId);
} }
/// <summary>
/// Clears the window with the currently set background color
/// </summary>
public void Clear () {
NCurses.ClearWindow (this);
Draw ();
}
/// <summary> /// <summary>
/// Destroys this window and all children windows /// Destroys this window and all children windows
/// <paramref name="skipParentRedrawing">Skips redrawing the windows parent if true</paramref>
/// </summary> /// </summary>
public void Destroy () { public void Destroy (bool skipParentRedrawing = false) {
// Catch double destroy calls // Catch double destroy calls
if (WindowId == -1) throw new Exception ("Destroy called twice on object"); if (WindowId == -1) throw new Exception ("Destroy called twice on object");
foreach (var window in _childWindows.ToList ()) { foreach (var window in _childWindows.ToList ()) {
window.Destroy (); window.Destroy (true);
} }
UnregisterInputHandler (); UnregisterInputHandler ();
@ -262,7 +291,7 @@ public class Window {
// Ensures sure the parent is updated too // Ensures sure the parent is updated too
if (ParentWindow is not null) { if (ParentWindow is not null) {
ParentWindow.RemoveChildWindow (this); ParentWindow.RemoveChildWindow (this);
ParentWindow.Draw (); if (!skipParentRedrawing) ParentWindow.Draw ();
} }
} }
@ -335,7 +364,8 @@ public class Window {
/// <returns>Usable inner width of window in columns</returns> /// <returns>Usable inner width of window in columns</returns>
public int GetUsableWidth () { public int GetUsableWidth () {
if (BorderEnabled) { if (BorderEnabled) {
return WindowSize.Width - 1 - InnerPadding; // Total window with - (1col Border Left and Right) - (InnerPadding Left and Right)
return WindowSize.Width - 2 - (InnerPadding * 2);
} else { } else {
return WindowSize.Width; return WindowSize.Width;
} }
@ -347,9 +377,31 @@ public class Window {
/// <returns>Usable inner height of window in rows</returns> /// <returns>Usable inner height of window in rows</returns>
public int GetUsableHeight () { public int GetUsableHeight () {
if (BorderEnabled) { if (BorderEnabled) {
return WindowSize.Height - 1 - InnerPadding; // Total window with - (1col Border Top and Bottom) - (InnerPadding Top and Bottom)
return WindowSize.Height - 2 - (InnerPadding * 2);
} else { } else {
return WindowSize.Height; 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 ();
}
} }

@ -2,6 +2,8 @@
using SCI.CursesWrapper.UiElements; using SCI.CursesWrapper.UiElements;
using ANSI_Fahrplan.Screens; using ANSI_Fahrplan.Screens;
using Mindmagma.Curses; using Mindmagma.Curses;
using System.Reflection;
using Microsoft.Win32.SafeHandles;
namespace ANSI_Fahrplan; namespace ANSI_Fahrplan;
@ -19,8 +21,7 @@ class Program {
**/ **/
var screen = CursesWrapper.InitNCurses (); var screen = CursesWrapper.InitNCurses ();
//var topLevelWindows = new List<Window> ();
// -- Screen-wide input handler -- // // -- Screen-wide input handler -- //
var inputHandler = new InputHandler (); var inputHandler = new InputHandler ();
inputHandler.StartListening (); inputHandler.StartListening ();
@ -34,16 +35,17 @@ class Program {
Environment.Exit (0); Environment.Exit (0);
}; };
// -- Inner Window -- // // -- Content Window -- //
// //
// This window contains all the dynamic content between // This window contains all the dynamic content between
// the menu and status bars // the menu and status bars
var innerWindow = new InnerWindow (screen, inputHandler) { var contentWindow = new ContentWindow (screen, inputHandler) {
BorderEnabled = true BorderEnabled = true
}; };
// -- Intro screen -- // // -- Intro screen -- //
var introScreen = new IntroScreen (innerWindow); var introScreen = new IntroScreen (contentWindow);
introScreen.Draw ();
// Close intro screen on any keypress // Close intro screen on any keypress
introScreen.OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => { introScreen.OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => {
@ -56,10 +58,14 @@ class Program {
// Wait until intro screen is closed // Wait until intro screen is closed
while (introScreen.WindowId > -1) Thread.Sleep (50); // TODO; Unjank this while (introScreen.WindowId > -1) Thread.Sleep (50); // TODO; Unjank this
// -- Create menu bar -- // // -- Create menu bar -- //
var topMenu = new TopMenu (screen, inputHandler, CreateMenuItems (innerWindow)); var topMenu = new TopMenu (screen, inputHandler, CreateMenuItems (contentWindow));
// Testing
var testwin = contentWindow.CreateInnerWindow ();
testwin.BackgroundColorId = ColorSchemes.TextInputField ();
NCurses.WindowAddString (testwin, "Hello World.");
contentWindow.Draw ();
// Wait until the input handler routine stops // Wait until the input handler routine stops
while (inputHandler.IsListening ()) Thread.Sleep (50); while (inputHandler.IsListening ()) Thread.Sleep (50);
@ -69,17 +75,23 @@ class Program {
Environment.Exit (1); Environment.Exit (1);
} }
private static List<MenuItem> CreateMenuItems (InnerWindow innerWindow) { private static List<MenuItem> CreateMenuItems (ContentWindow contentWindow) {
var helpItem = new MenuItem ("Help", "F1"); var helpItem = new MenuItem ("Help", "F1");
var upcomingItem = new MenuItem ("Upcoming", "b"); //F2 var upcomingItem = new MenuItem ("Upcoming", "b"); //F2
var byDayItem = new MenuItem ("By Day", "n"); // F3 var byDayItem = new MenuItem ("By Day", "n"); // F3
var byRoomItem = new MenuItem ("By Room", "F4"); var byRoomItem = new MenuItem ("By Room", "F4");
var bySpeakerItem = new MenuItem ("By Speaker", "F5"); var bySpeakerItem = new MenuItem ("By Speaker", "F5");
var byTestItem = new MenuItem ("By Test", "F7");
var quitItem = new MenuItem ("Quit (C-q)"); var quitItem = new MenuItem ("Quit (C-q)");
helpItem.OnItemActivated += (object sender, MenuItemActivatedEventArgs e) => { 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 ();
}; };
return new List<MenuItem> { return new List<MenuItem> {

@ -1,3 +1,4 @@
using Mindmagma.Curses;
using SCI.CursesWrapper; using SCI.CursesWrapper;
namespace ANSI_Fahrplan.Screens; namespace ANSI_Fahrplan.Screens;
@ -15,11 +16,16 @@ public class IntroScreen : Window {
parentWindow parentWindow
) )
{ {
string asciiArt = SCI.AsciiArt.Generator.FromImage (
"res" + Path.DirectorySeparatorChar + "38c3.jpg",
GetUsableWidth ()
).Result;
string asciiArtPlusText = string asciiArtPlusText =
"Press F1 for a quick start guide or simply press Enter\n" + "Press F1 for a quick start guide or simply press Enter\n" +
"to see upcoming events\n" + "to see upcoming events\n" +
AsciiArt.logo38c3_80x24 + asciiArt +
"\n38C3 Fahrplan in your terminal!"; "38C3 Fahrplan in your terminal!";
AsciiArt.ShowCentered (WindowId, asciiArtPlusText); AsciiArt.ShowCentered (WindowId, asciiArtPlusText);
} }

@ -1,8 +0,0 @@
namespace ANSI_Fahrplan.Screens;
public abstract class ScrollableScreen : Screen {
public ScrollableScreen (nint rootWindow) : base (rootWindow) {}
}

@ -4,7 +4,7 @@ public abstract class UiElement {
private nint _screen; private nint _screen;
public nint Screen { get { return _screen; }} public nint Screen { get { return _screen; }}
protected nint innerWindow { get; set; } protected nint contentWindow { get; set; }
public UiElement (nint screen) { public UiElement (nint screen) {
_screen = screen; _screen = screen;

@ -11,6 +11,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="dotnet-curses" Version="1.0.3" /> <PackageReference Include="dotnet-curses" Version="1.0.3" />
<None Update="res/38c3.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Loading…
Cancel
Save