What's new in 3.0
A release about what the terminal can be talked into. Most of it is new surface that an application gets without editing a line — Migrating to 3.0 is the short list of what does need an edit, and the changelog is the full record.
Pictures
Picture draws an image. By default it needs nothing of the terminal beyond the color it already
draws in: a cell carries two pixels, the upper half block painted as the one above and its background
as the one below.
private readonly Picture _preview = new();
_preview.Show(pixels, width, height);
_preview.Draw(region);
Where the terminal speaks a graphics protocol, the pixels themselves go out instead — the kitty
graphics protocol on kitty, WezTerm and Ghostty, and sixel on Windows Terminal, xterm and foot. The
default is Auto: whichever the terminal admitted to, and cells when it admitted to nothing, so a
picture appears either way.
Underneath is Surface.Passthrough, for what the cell grid cannot express. It takes what removes the
payload as well as the payload, so a picture whose view left the screen is undrawn rather than left
behind — kitty deletes by number, sixel paints over, cells need nothing. See
Pictures.
The terminal is asked what it can do
Once, before the first frame, and it decides more than pictures: which graphics protocols the terminal speaks, how many pixels a cell is, and what color is behind the text.
The arrangement that makes it safe is the order. The questions end with the one every terminal
answers — primary device attributes — so the reply to it is the signal that no other reply is coming.
Without that fence there is nothing to wait for but a guess at how long a terminal takes to stay
silent. A terminal that answers nothing costs TerminalAnswer, 120 ms by default, and leaves every
setting as it was. Nothing a person typed is swallowed either: whatever was read is handed straight
back unless the fence came.
options.AskTerminal = false; // decide yourself instead
Panes that touch share a line
A PaneTree with Gaps(inner: 0) used to put ╮╭ where the eye expects ┬, because each titled
pane drew its own box. The tree now records its boxes in a Joinery and paints them together, and
panes that touch are pulled onto one another's edge so the line is shared. Nothing to change — a
layout that already asked for no gap simply looks right.
The look and the state are checked
Everything process-wide that a frame reads is now written on the drawing thread and says so when it
is not. Theme.Palette, Glyphs.Graph, Glyphs.Picture and the cell size join ArlecchinoState,
its modals and the notification list: each asks which thread it is on and each asks for a frame by
itself.
The practical effect is that a background task that changes the theme or opens a dialog now fails loudly instead of tearing a frame in half. Hand the change over the way an atom write is handed over:
FrameThread.Post(() => Theme.Palette = ThemePalette.Basic);
The dialog stack became a LocalAtomsList<Modal> at the same time, so what watches it is told when it
changes rather than after the fact.
Any language can be typed without asking
TextInputMode.Native is the default, so a layout that is not Latin works out of the box. What was
UseLatinOnlyInput() is UseKeysByPosition(), and it now does without exception what it used to do
only sometimes — the position of a key decides, whatever the layout makes of it. See
Keyboard.
A test written as the session it describes
SessionTape in Arlecchino.Testing records the events that go in, how long the application waits
for them, and where a frame is worth looking at:
var frames = new SessionTape()
.Type(":")
.Shot()
.Type("copy")
.Wait(200)
.Shot()
.Play(host);
Playing it draws the same frames every time, because a screen is a function of state, state changes only on an event, and the clock comes from a provider. See Testing.
Added
| What | Where |
|---|---|
Picture, ImageProtocol | Pictures |
Surface.Passthrough(row, column, payload, undraw) | Rendering |
TerminalProbe, TerminalCapabilities | Pictures |
Joinery | Layout |
SessionTape | Testing |
IArlecchinoTerminal.Unread(key) | API |
CellWidth, CellHeight, ImageProtocol, AskTerminal, TerminalAnswer options | Hosting and options |
Fixed
- A picture could vanish from a frame that was written whole — writing every cell is what removes the pixels over it in some terminals, so every payload goes out again when the frame does.
- A picture drawn in cells was written out again every frame, however still it was: it built its colors afresh and the frame diff tells cells apart by reference.
- Undrawing a sixel could paint up to five rows below it, because bands are six rows whatever the picture's height.
- The probe assumed answers came back in the order they were asked for; nothing in any specification says they must.
- A console read that failed reached views as a key press of NUL, which no one presses.
What came just before it
2.13.0 added AreaChart — a series drawn as a filled area over as many rows as it is given, which
is the shape a system monitor shows and the one thing Sparkline cannot be. See
Charts.