yet more changes
This commit is contained in:
parent
a6377d1a57
commit
d0853e23bc
39
AsciiArt/Generator.cs
Normal file
39
AsciiArt/Generator.cs
Normal file
@ -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 () {
|
||||
return NCurses.ColorPair (4);
|
||||
}
|
||||
|
||||
public static uint CustomColorTest () {
|
||||
return NCurses.ColorPair (21);
|
||||
}
|
||||
}
|
37
CursesWrapper/ContentWindow.cs
Normal file
37
CursesWrapper/ContentWindow.cs
Normal file
@ -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
|
||||
)
|
||||
{ }
|
||||
}
|
109
CursesWrapper/ScrollWindow.cs
Normal file
109
CursesWrapper/ScrollWindow.cs
Normal file
@ -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>
|
||||
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>
|
||||
@ -163,13 +170,25 @@ public class Window {
|
||||
/// <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;
|
||||
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 + addedPadding, position.X + addedPadding
|
||||
position.Y, position.X
|
||||
);
|
||||
|
||||
parentWindow.AddChildWindow (this);
|
||||
@ -186,6 +205,7 @@ public class Window {
|
||||
|
||||
NCurses.Keypad (WindowId, true);
|
||||
|
||||
Clear ();
|
||||
Draw ();
|
||||
}
|
||||
|
||||
@ -236,14 +256,23 @@ public class Window {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys this window and all children windows
|
||||
/// Clears the window with the currently set background color
|
||||
/// </summary>
|
||||
public void Destroy () {
|
||||
public void Clear () {
|
||||
NCurses.ClearWindow (this);
|
||||
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 ();
|
||||
window.Destroy (true);
|
||||
}
|
||||
|
||||
UnregisterInputHandler ();
|
||||
@ -262,7 +291,7 @@ public class Window {
|
||||
// Ensures sure the parent is updated too
|
||||
if (ParentWindow is not null) {
|
||||
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>
|
||||
public int GetUsableWidth () {
|
||||
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 {
|
||||
return WindowSize.Width;
|
||||
}
|
||||
@ -347,9 +377,31 @@ public class Window {
|
||||
/// <returns>Usable inner height of window in rows</returns>
|
||||
public int GetUsableHeight () {
|
||||
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 {
|
||||
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 ();
|
||||
}
|
||||
}
|
34
Program.cs
34
Program.cs
@ -2,6 +2,8 @@
|
||||
using SCI.CursesWrapper.UiElements;
|
||||
using ANSI_Fahrplan.Screens;
|
||||
using Mindmagma.Curses;
|
||||
using System.Reflection;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace ANSI_Fahrplan;
|
||||
|
||||
@ -19,8 +21,7 @@ class Program {
|
||||
**/
|
||||
|
||||
var screen = CursesWrapper.InitNCurses ();
|
||||
//var topLevelWindows = new List<Window> ();
|
||||
|
||||
|
||||
// -- Screen-wide input handler -- //
|
||||
var inputHandler = new InputHandler ();
|
||||
inputHandler.StartListening ();
|
||||
@ -34,16 +35,17 @@ class Program {
|
||||
Environment.Exit (0);
|
||||
};
|
||||
|
||||
// -- Inner Window -- //
|
||||
// -- Content Window -- //
|
||||
//
|
||||
// This window contains all the dynamic content between
|
||||
// the menu and status bars
|
||||
var innerWindow = new InnerWindow (screen, inputHandler) {
|
||||
var contentWindow = new ContentWindow (screen, inputHandler) {
|
||||
BorderEnabled = true
|
||||
};
|
||||
|
||||
// -- Intro screen -- //
|
||||
var introScreen = new IntroScreen (innerWindow);
|
||||
var introScreen = new IntroScreen (contentWindow);
|
||||
introScreen.Draw ();
|
||||
|
||||
// Close intro screen on any keypress
|
||||
introScreen.OnKeyPress += (object sender, NCursesKeyPressEventArgs e) => {
|
||||
@ -56,10 +58,14 @@ class Program {
|
||||
// Wait until intro screen is closed
|
||||
while (introScreen.WindowId > -1) Thread.Sleep (50); // TODO; Unjank this
|
||||
|
||||
|
||||
|
||||
// -- 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
|
||||
while (inputHandler.IsListening ()) Thread.Sleep (50);
|
||||
@ -69,17 +75,23 @@ class Program {
|
||||
Environment.Exit (1);
|
||||
}
|
||||
|
||||
private static List<MenuItem> CreateMenuItems (InnerWindow innerWindow) {
|
||||
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 byRoomItem = new MenuItem ("By Room", "F4");
|
||||
var bySpeakerItem = new MenuItem ("By Speaker", "F5");
|
||||
var byTestItem = new MenuItem ("By Test", "F7");
|
||||
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 ();
|
||||
};
|
||||
|
||||
return new List<MenuItem> {
|
||||
|
@ -1,3 +1,4 @@
|
||||
using Mindmagma.Curses;
|
||||
using SCI.CursesWrapper;
|
||||
|
||||
namespace ANSI_Fahrplan.Screens;
|
||||
@ -15,11 +16,16 @@ public class IntroScreen : Window {
|
||||
parentWindow
|
||||
)
|
||||
{
|
||||
string asciiArt = SCI.AsciiArt.Generator.FromImage (
|
||||
"res" + Path.DirectorySeparatorChar + "38c3.jpg",
|
||||
GetUsableWidth ()
|
||||
).Result;
|
||||
|
||||
string asciiArtPlusText =
|
||||
"Press F1 for a quick start guide or simply press Enter\n" +
|
||||
"to see upcoming events\n" +
|
||||
AsciiArt.logo38c3_80x24 +
|
||||
"\n38C3 Fahrplan in your terminal!";
|
||||
asciiArt +
|
||||
"38C3 Fahrplan in your terminal!";
|
||||
|
||||
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;
|
||||
public nint Screen { get { return _screen; }}
|
||||
|
||||
protected nint innerWindow { get; set; }
|
||||
protected nint contentWindow { get; set; }
|
||||
|
||||
public UiElement (nint screen) {
|
||||
_screen = screen;
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dotnet-curses" Version="1.0.3" />
|
||||
<None Update="res/38c3.jpg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
BIN
res/38c3.jpg
Normal file
BIN
res/38c3.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Loading…
x
Reference in New Issue
Block a user