Skip to content

mstrobel/Cursorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

535 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cursorial

A cross-platform .NET library for building visually rich terminal applications with first-class mouse and keyboard support.

Terminal development is deceptively hard. Capability detection, VT-vs-Win32 input, grapheme widths, wide-glyph alignment, raw-mode and signal-handler safety, and a long tail of per-terminal quirks all stand between you and a polished UI. Cursorial absorbs that complexity so you don't have to. Whether you're building a TUI framework or an application, use as much of the stack as you need — take just the input handling, opt in to the rendering and drawing layers, or build a declarative, data-bound UI in XAML.

Cursorial is library-first: designed to be consumed by app and framework authors, not used directly as an end-user tool. It targets Windows, macOS, and Linux terminals as equal peers — design choices that would bake in single-platform assumptions (VT-only, Win32-only) need an explicit story for the others before they land.

📖 Documentation lives in the Wiki. This README is the tour; the wiki has the deep per-layer guides, tutorials, and API walkthroughs.

Layered design

The library is a stack. Each layer builds on the one below it, and each is usable on its own — you only reference what you intend to use.

Layer Package Role
Cursorial.Core ✅ On NuGet Input parsing & delivery, output sequence emission, capability negotiation, session orchestration, text utilities.
Cursorial.Rendering ⏳ In-repo Cell buffer, diffing frame renderer, rich-text formatting, images, blending & alpha compositing.
Cursorial.Drawing ⏳ In-repo Scenes & cached-raster compositor, brushes & gradients, a pen/box engine with automatic junctions, charts, and UI drawing primitives (clip/translate, occluding & image fills, titled panels, shadows).
Cursorial.Animation ⏳ In-repo A time-free animation engine — easings, keyframes, and value interpolators (the consumer owns the clock).
Cursorial.UI ⏳ In-repo A WPF/Avalonia-style UI framework: dependency-property system, element tree, layout, render zones, routed input & focus, styling with CSS-like selectors, data binding, resources & theming, windowing, storyboards & transitions, and a full control catalog.
Cursorial.UI.Xaml ⏳ In-repo A runtime XAML loader and a Roslyn source generator — declarative markup, {Binding}/{StaticResource}/ControlTemplate, typed code-behind, compiled bindings, and an AOT-clean metadata provider.
Cursorial.UI.Bars ⏳ In-repo Command surfaces over one shared BarCommand set: a Toolbar with discrete overflow, a Ribbon (tabs/groups, density collapse, contextual tabs, Backstage, Quick Access Toolbar, minimize), KeyTips (Alt-overlay accelerators), and SuperTips.
dotnet add package Cursorial.Core

Only Cursorial.Core is published today, and its surface is approaching stable. The layers above it are built and exercised in-repo (the Cursorial.Demo REPL and Cursorial.Gallery app drive them end-to-end) but are pre-release and not yet packaged — clone the repo to use them, and expect breaking changes. Feedback very welcome.

A taste

// Raw mode, capability handshake, opt-in protocols, signal-safe restore — all in one call.
await using var session = await TerminalSession.OpenAsync();
var caps = session.Capabilities;

var buffer = new CellBuffer(Console.WindowWidth, Console.WindowHeight, caps);
var renderer = new FrameRenderer(caps.Output);

buffer.Write(0, 0, "Hello, terminal 👋",
             Style.Default.WithForeground(Color.FromRgb(64, 224, 208))
                          .WithAttributes(TextAttributes.Bold));

var frame = new ArrayBufferWriter<byte>();
renderer.Render(buffer, frame);
await session.Output.Writer.WriteAsync(frame.WrittenMemory);
await session.Output.Writer.FlushAsync();

await foreach (var evt in session.Input.ReadAllAsync())
    if (evt is KeyEvent { Kind: KeyEventKind.Down, Text.Span: "q" }) break;

TerminalSession.OpenAsync() puts stdio into raw mode, runs the capability handshake, and applies the opt-in protocols (mouse, focus, paste, Kitty keyboard, …) the negotiator's policy allows. Disposal restores everything it touched — termios / Windows console state, every protocol opt-in — in LIFO order, and signal handlers (SIGINT / SIGTERM / SIGHUP / SIGQUIT / AppDomain.ProcessExit) are registered automatically so the terminal isn't left in raw mode on abnormal exit. A bring-your-own-transport overload — TerminalSession.OpenAsync(source, sink) — embeds the pipeline inside a tool that already owns terminal state, or drives it from a recorded trace.


Cursorial.Core — the foundation

Everything you need to read input, negotiate capabilities, and emit correct escape sequences. The published, integration-ready layer.  → Wiki: Input · Capabilities · Output · Text

Input. InputEvent is a sealed record hierarchy — pattern-match on the concrete type. KeyEvent (down/up/repeat, full modifiers, Text payload; legacy xterm, modifyOtherKeys, CSI u, Kitty keyboard, and Win32 Input Mode), MouseEvent (press/release/drag/motion/wheel, buttons X1–X4, SGR + SGR-Pixels), plus FocusEvent, PasteEvent, ResizeEvent, and device responses. Unrecognized sequences surface as UnknownEvent with the wire bytes — nothing is silently swallowed. Pick a delivery shape per device: pull (IAsyncInputDevice.ReadAllAsync, await foreach) or push (IEventInputDevice, classic events). Transforms (device.WithClickSynthesis(…), KeyReleaseSynthesizer) layer click-counting and synthetic key-up/repeat on top.

await foreach (var evt in session.Input.ReadAllAsync(ct))
    switch (evt)
    {
        case KeyEvent { Kind: KeyEventKind.Down } k: HandleKey(k); break;
        case MouseEvent m:                           HandleMouse(m); break;
        case ResizeEvent r:                          HandleResize(r); break;
    }

Capability negotiation. VtTerminalNegotiator runs an XTVERSION + DA1 sentinel handshake, identifies the terminal family (Kitty, iTerm2, WezTerm, Ghostty, Rio, Windows Terminal, xterm, tmux, Apple Terminal, …), and enables only the protocols that family honors (SGR mouse, focus, bracketed paste, Kitty keyboard, Win32 Input Mode, synchronized output). It returns a TerminalCapabilities aggregate of realized capabilities — features a terminal claims but doesn't honor are reported unavailable — and restore is idempotent, reversing every opt-in.

Output writers. Pure byte-emitters targeting IBufferWriter<byte>: SgrEncoder (full + minimal-delta), CursorWriter, ScreenWriter, HyperlinkWriter (OSC 8), TextSizingWriter (Kitty OSC 66). The Style value type composes foreground/background/attributes/underline; Color spans Default, FromPalette(0–255), FromRgb, and FromRgba (alpha for blending). StyleQuantizer adapts a style down to what the terminal can actually render.

Text utilities. GraphemeWidth (cluster-accurate widths, VS15/VS16, ZWJ, CJK & emoji) and AnsiTextWrap (word-wrap that measures by grapheme cluster and passes ANSI escapes through untouched).


Cursorial.Rendering — drawing to a cell buffer

A cell-buffer + diffing renderer, plus the content types you draw into it: rich text, images, and sized text.  → Wiki: Rendering · Rich Text & Images

Write into a CellBuffer; hand successive buffers to a stateful FrameRenderer that diffs them and emits the minimal byte stream to update the terminal (including scroll-region detection). Writes are grapheme-aware, wide-cell consistent, and capability-quantized before diffing, so a stable frame produces an empty delta even on a 16-color terminal. buffer.View(…) returns a clipped, view-local CellBufferView so a widget draws in its own coordinate space without knowing where it sits.

var buffer = new CellBuffer(columns: 80, rows: 24, caps);
buffer.Write(0, 0, "中文 and 👨‍👩‍👧 emoji", style);   // wide glyphs + clusters advance correctly
renderer.Render(buffer, frame);                       // full redraw on frame 1 / resize; per-cell delta after

On top of the buffer sit rich text (TextMarkup.Parse / RichTextBuilderTextFormatter → paint, with inline tags, wrapping/alignment, and OSC 8 hyperlinks that survive a line split), images (Image / Icon pick Kitty graphics → iTerm2 → Sixel at paint time, with glyph fallback and diff-skip across frames), sized text (ScaledText — Kitty OSC 66 where supported, a bundled FIGlet face elsewhere), and blending & alpha compositing (PushBlendingMode with Multiply/Screen/Overlay/… over RGB pairs).


Cursorial.Drawing — scenes, brushes, charts

A retained-scene drawing layer over the cell buffer: the unit of work is a Scene (a cached raster) composited by a SceneCompositor that only repaints what changed.  → Wiki: Drawing

scene.Draw(ctx =>
{
    ctx.FillRectangle(new Rect(0, 0, 20, 6), new LinearGradientBrush(Colors.Cyan, Colors.Blue));
    ctx.DrawBox(new Rect(0, 0, 20, 6), Pens.Light);            // junctions auto-resolve where strokes meet
    ctx.DrawText(2, 2, "Cached & composited", Colors.White);
});
  • Brushes & pensSolidColorBrush, LinearGradientBrush/RadialGradientBrush (aspect-corrected), TileBrush/Image; a Pen engine that forms box-drawing junctions automatically across separate strokes within a figure.
  • Charts — line / scatter / bar (signed, NaN-gap), category axes, fill areas — in Cursorial.Drawing.Charts.
  • CompositorScene + SceneCompositor maintain the compositing invariant (always composite onto base, so retained translucent scenes don't drift) with cached rasters and a pooled ScenePool.
  • UI primitives — an intra-scene clip/translate stack honored by every draw path, opaque/occluding fills, dither, titled panels, and drop/inner shadows; brush-aware formatted text and images.

Cursorial.Animation — a time-free animation engine

Pure animation primitives with no ambient clock: IAnimation<T> maps elapsed time → value, so the consumer (or Cursorial.UI) owns the clock and the layer stays deterministic and testable.  → Wiki: Animation

IAnimation<double> fade = new Animation<double>(from: 0.0, to: 1.0, duration: TimeSpan.FromMilliseconds(250),
                                                Interpolator.For<double>(), Easings.QuadOut);
double v = fade.ValueAt(elapsed);   // sample wherever your clock is

Easings (cubic/quad/elastic/bounce/cubic-bezier(…)), a process-global Interpolator registry (double, int, Color, Point/Size/Rect, Margins, IBrush, Pen, …), and combinators (Delay, Then, repeat). The UI layer drives these through a frame-aligned scheduler with storyboards and implicit transitions (below).


Cursorial.UI — a terminal UI framework

A WPF/Avalonia-style framework on top of Cursorial.Drawing. If you've used either, the shapes will feel familiar: a dependency-property system, an element tree with measure/arrange layout, retained render zones, routed input, CSS-like styling, data binding, and a control catalog.  → Wiki: UI Overview · Layout & Panels · Controls · Styling & Themes · Data Binding · Input & Focus · Windowing · Animation & Transitions

var app = UIApplication.CreateBuilder().WithFrameRate(60).Build();

return await app.RunAsync(() =>
{
    var stack = new StackPanel { Spacing = 1, Margin = new Margins(2, 1) };
    var button = new Button { Content = "_Click me" };          // _ = Alt access key
    button.Click += (_, _) => button.Content = "Clicked!";

    stack.Children.Add(new TextBlock { Text = "Hello, Cursorial.UI 👋" });
    stack.Children.Add(button);
    return stack;
});
  • Property system & layout — styled / attached / direct dependency properties with a priority-ordered value store; Measure/Arrange; panels: StackPanel, DockPanel, Grid, Canvas, WrapPanel, VirtualizingStackPanel.
  • ControlsButton/ToggleButton/CheckBox/RadioButton, TextBox/PasswordBox, ScrollViewer, ListBox/ComboBox/TreeView, TabControl, Menu/ContextMenu/ToolTip, Slider/ProgressBar, Expander/GridSplitter/StatusBar, Calendar/DatePicker, and ItemsControl with container virtualization.
  • StylingStyle/Setter with a CSS-like selector grammar (type / .class / #name / :pseudo-class / descendant & child / :is()), pseudo-classes driven by interaction state (:pointerover, :focus, :checked), When data conditions, resources ({StaticResource}/{DynamicResource}), and theme variants (light/dark, color tiers).
  • Data binding{Binding} with paths, converters, modes, RelativeSource, ElementName/x:Reference, INotifyPropertyChanged, plus typed zero-allocation compiled bindings.
  • Input & focus — bubbling/tunneling routed events, focus scopes & directional navigation, KeyGesture commands & InputBindings, access keys, and element-level mouse cursors.
  • WindowingWindow (draggable/resizable/maximizable chrome, modal ShowDialogAsync), light-dismiss Popups, drop shadows, and a window manager that composites a surface stack — all without OS windows.
  • Animation — storyboards and implicit Transitions over the property system (e.g. fade Opacity on :pointerover), on a frame-aligned scheduler with reduced-motion support.
  • Command surfaces (Cursorial.UI.Bars) — a Toolbar with discrete overflow and a Ribbon (tabs, groups with a density-collapse tier, contextual tabs, Backstage, a Quick Access Toolbar, and a minimizable band), plus KeyTips (Alt-overlay accelerator badges) and SuperTips — all bound to one shared BarCommand set (define once, surface anywhere).

Cursorial.UI.Testing provides a headless UITestHost (a synthetic terminal on a fake clock) so the whole framework — layout, input, rendering — is unit-testable without a TTY.


Cursorial.UI.Xaml — declarative markup

Define your UI in XAML, loaded at runtime or compiled by a Roslyn source generator.  → Wiki: XAML

<StackPanel xmlns="https://cursorial.dev/ui" Spacing="1" Margin="2,1">
  <TextBlock Text="{Binding Title}" />
  <Button Content="_Save" Command="{Binding SaveCommand}" Background="{StaticResource AccentBrush}" />
</StackPanel>
var root = XamlLoader.Shared.Load<StackPanel>(xaml);
root.DataContext = viewModel;
  • Runtime loader{Binding} / {StaticResource} / {DynamicResource} / {TemplateBinding}, ControlTemplates, ResourceDictionary with merged & theme dictionaries, styles with the full selector grammar, access-key folding, and line+column diagnostics on parse/semantic errors.
  • Source generator — build-time XAML validation as compiler diagnostics, typed x:Name fields + InitializeComponent() code-behind (WPF-style x:Class), generator-emitted compiled bindings with x:DataType path checking, and an AOT-clean metadata provider (no reflection at runtime). The generator runs the same parser as the loader over Roslyn symbols, and a dual-run gate asserts the two produce byte-identical trees.
  • Theming — built-in control themes authored in box-drawing idiom, overridable per-app; ship themes as code or as embedded XAML.

Project layout

Project Contents
Cursorial.Core Input parsing & delivery, output writers, capability negotiation, terminal session, text utilities.
Cursorial.Rendering Cell buffer, frame renderer, rich text, images, blending & alpha compositing.
Cursorial.Drawing Scenes & compositor, brushes/gradients, pen/junction engine, charts, UI drawing primitives.
Cursorial.Animation Time-free IAnimation<T>, easings, interpolator registry.
Cursorial.UI The UI framework: property system, layout, controls, styling, binding, input/focus, windowing, animation.
Cursorial.UI.Xaml.Frontend The shared netstandard2.0 XAML parser, node model, and type-system seams.
Cursorial.UI.Xaml The net10.0 runtime XAML loader (markup extensions, resources, templates).
Cursorial.UI.Xaml.Generator The Roslyn source generator (compiled bindings, code-behind, AOT-clean provider).
Cursorial.UI.Bars Command surfaces: Toolbar with overflow, Ribbon (density collapse, contextual tabs, Backstage, QAT, minimize), KeyTips, SuperTips.
Cursorial.UI.Themes Bundled UI themes (data-shipped XAML overlay over the code-first built-in theme).
Cursorial.UI.Testing Headless UITestHost + synthetic terminal for testing UI without a TTY.
Cursorial.Shared Markup attributes shared by the loader and generator.
Cursorial.Demo Interactive REPL that drives every layer end-to-end (see below).
Cursorial.Demo.XamlAot / .XamlAotStrict NativeAOT publish demos — the reflection loader, and the reflection-free build on the generated metadata provider (the AOT-clean exit gate).
Cursorial.Gallery A standalone XAML-first MVVM control gallery — a full app, not a demo command.
*.Tests xUnit suites per project.

Requirements

  • .NET SDK 10.0.100 or later (pinned in global.json with rollForward: latestMinor).
  • A terminal. The suite has been exercised against kitty, Ghostty, iTerm2, WezTerm, Rio, Apple Terminal, Alacritty, GNOME Terminal, ConEmu/Cmder, and Windows Terminal / Console Host.

Building and testing

dotnet build
dotnet test

Trying the demos

dotnet run --project Cursorial.Demo     # the interactive REPL
dotnet run --project Cursorial.Gallery  # the standalone control gallery

The demo opens an interactive prompt. Useful commands:

Command What it does
negotiate Run the negotiator and dump the realized capabilities.
read / raw / trace Decoded input events, raw stdin bytes, or a live side-by-side of both.
render Diff-rendered showcase: palette, truecolor gradient, wide glyphs, attributes, alpha, images, sized text.
draw / charts / brushtext Cursorial.Drawing scenes, charts, and brush-aware text.
imagescene / imageclip / sizing Image compositing, clipping, and Kitty OSC 66 sized text.
animate Cursorial.Animation showcase.
ui Cursorial.Drawing UI primitives — gradient FIGlet, titled panels + shadows, an occluding opaque modal.
uipanels / uicontrols / gallery The Cursorial.UI panel tree, control set, and full control gallery on the real frame loop.
uixaml A control tree loaded at runtime from an embedded .xaml resource.
motion The Cursorial.UI animation showcase — storyboards, implicit transitions, edge-action pulse.
windows The S4 windowing showcase — draggable/resizable windows, modal dialogs, light-dismiss popups.
inspect A live XAML / element-tree inspector.
probe / accesskeys / rasterbench Protocol probes, the access-key capability gate, and a raster benchmark.
help / quit Self-explanatory.

Each command opens its own raw-mode TerminalSession and restores cooked mode before the next prompt.

License

Apache License 2.0. See LICENSE for the full text.

About

Cursorial is a cross-platform .NET library for building high-quality, visually rich terminal applications with robust mouse support. Use the input/output layer as the foundation for your own TUI framework, or go farther and opt in to the rendering library.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages