Getting started
Install
dotnet add package Arlecchino
Arlecchino pulls in Arlecchino.Core (the renderer) and carries the source generator inside the package,
so nothing else has to be referenced. Microsoft.Extensions.Hosting is what you need for the host
itself.
Two packages are added only when they are wanted: Arlecchino.Pictures reads image files into the
pixels a picture draws, and Arlecchino.Testing is the headless host the
tests of your application run on.
The smallest app
using Arlecchino.Hosting;
using Microsoft.Extensions.Hosting;
using MyApp.Navigation; // ViewKind and AddGeneratedViews are generated here
var builder = Host.CreateApplicationBuilder(args);
builder.Services
.AddArlecchino(options => options.MinimumWidth = 60)
.AddGeneratedViews()
.AddGeneratedStores()
.AddGeneratedCommands()
.StartAt(ViewKind.Default);
await builder.Build().RunAsync();
AddArlecchino registers the renderer, the navigator, the input router and a hosted service that owns
the render loop. AddGeneratedViews plugs in the factory emitted by the generator,
AddGeneratedStores registers every IArlecchinoStore it found alongside it,
AddGeneratedCommands does the same for every IArlecchinoCommand, and StartAt
picks the route shown on the first frame. Everything after AddArlecchino is a call on
ArlecchinoBuilder — see Hosting and options.
That first using is the one thing not visible from the code: ViewKind and AddGeneratedViews are
written by the generator into $(RootNamespace).Navigation, not into a namespace of the package, so
the file wiring the application up has to import it. Both exist from the moment the package is
referenced — before the first view is written ViewKind simply holds no routes and the generator
says so as ARL004. See Source generator to put them somewhere else.
The first view
A view is a class implementing IArlecchinoView. Constructor parameters are resolved from the container:
using Arlecchino.Input;
using Arlecchino.Navigation;
using Arlecchino.Rendering;
using Arlecchino.Rendering.Colors;
using MyApp.Navigation;
public class DefaultView : IArlecchinoView
{
private readonly Surface _surface;
public DefaultView(Surface surface) => _surface = surface;
public void Draw()
{
_surface.AppendLine("hello", Theme.Header, Align.Center);
}
public ViewRoute Handle(KeyPress key) =>
key.Key == ConsoleKey.A ? ViewKind.About : ViewRoute.None;
public (string Key, string Description)[] Hints() => [("a", "about")];
}
ViewKind.About in that Handle is a second view: the routes are generated from the views that exist,
so until an AboutView sits beside this one the name is not there and the compiler says so. Return
ViewRoute.None from every key while there is one view.
Draw is called once per frame, Handle gets every key the framework did not consume itself, and the
route it returns navigates. Hints fills the box in the bottom-right corner. HandleMouse,
HandlePaste and Commands have defaults, so a view implements only what it uses. Details in
Views and navigation and Rendering.
The colors live in Arlecchino.Rendering.Colors and the surface in Arlecchino.Rendering; since
4.0 those are two namespaces rather than one, and it is the commonest thing to be missing a using
for — see Migrating to 4.0.
The DefaultView class name is what produces ViewKind.Default: the generator strips the View
suffix. The view itself may live in any namespace — the generated factory imports whatever it needs.
Set <ArlecchinoViewNamespace> in your csproj to choose where ViewKind lands instead of
$(RootNamespace).Navigation — see Source generator.
Running the samples
Three of them ship in the repository. Arlecchino.Sample is the gallery — a default view, an about view,
a settings form, the widget page, a command palette and the file picker:
dotnet run --project samples/Arlecchino.Sample
It also renders a single frame headlessly, which is the fastest way to look at a layout:
dotnet run --project samples/Arlecchino.Sample -- --frame picker 130x30
The frame goes to stdout as ANSI text; the view name is default, about, picker, or one of
password, number, slider, toggle, multi, date, time, color to render the matching
modal over the default view, and the size is <width>x<height>. Surface.SetFixedSize is what makes this possible in your own app — see
Rendering.
Arlecchino.Processes is the other kind of sample: a small application that does real work rather than
showing off widgets. It lists the processes on the machine in a sortable table, reads
them on a background thread through AsyncAtom with a spinner while it loads, filters them from a
text modal, and opens a details screen for the selected row:
dotnet run --project samples/Arlecchino.Processes
r re-reads the list, m and n sort, f filters, Enter opens the details. All four are
view commands, so they appear in the palette and in the hints box without
being written down twice. It renders headlessly too — --frame processes 110x26 or
--frame details 90x18.
Arlecchino.Commander is the largest of the
three, and the one with a repository of its own: a file manager with two panels over a local disk, an
SFTP server or an FTP one, with tabs, leader keys and a command line of its own. It takes the
framework from NuGet, the way an application of yours would.
dotnet run --project src/Arlecchino.Commander -- C:\some\folder C:\another
Each panel is a widget of its own and each dialog is a Modal of its own,
which is how it wears a look the framework does not bring; the menu behind F9 is one list per
section. Copy, move and delete ask their questions as modals and then run off the drawing thread,
reporting themselves as notifications with a bar and a key
that stops them.
It runs without the output line, so the bottom row belongs to the screen's own status bar. Every
screen renders headlessly as --frame 132x26, with --keys to play keys first and --connect to
open a panel on a server.