RasterExRatatui

Hex.pm Docs CI License

Render ExRatatui apps on pixel displays such as e-ink panels, with helpers for Linux framebuffers.

A terminal paints glyphs for us. A panel with nothing but pixels does not, so something has to turn every cell (a symbol, a foreground, a background) into pixels, and blit the bitmaps that Viewport3D and Image render. RasterExRatatui is that something. It sits on top of an ExRatatui.CellSession, keeps the cell grid and the pixel regions of the current frame, and hands the device only the rectangles that changed, already packed in the panel's pixel format.

Flow

flowchart LR
app("ExRatatui app") --> server["ExRatatui.Server"]
server -- "cell diff<br/>cells + pixel regions" --> raster["Raster<br/>font × palette × format × scale"]
raster -- "changed rectangles<br/>%Patch{}" --> push[["push/2"]]
push --> panel[/"the panel"/]
input[/"keyboard, touch<br/>Input.Devices"/] -- "key and mouse events" --> server

A surface (or a Session in a process the device already has) runs the app and owns this loop; the panel only ever sees packed pixels, in its own orientation.

Features

Installation

Add raster_ex_ratatui to the dependencies in mix.exs:

def deps do
[
{:raster_ex_ratatui, "~> 0.3"}
]
end

Prerequisites

Quick start

The app needs no change: it is a plain use ExRatatui.App that renders widgets exactly as it would in a terminal, so build and try it there first.

A Linux framebuffer (a Raspberry Pi with a display, on Nerves) is one module and a child in the supervision tree:

defmodule MyDevice.Surface do
use RasterExRatatui.Framebuffer.Surface, app: MyDevice.App, rotate: 90
end

It waits for /dev/fb0, reads the panel's size and depth from sysfs, picks the pixel format and a font scale, keeps the kernel console off the display, reads the first USB keyboard (and the touch panel, with touch: true) through input_event in the deps, and restarts the app when it quits. Every default is an option or an override. Nerves Quick Start goes from mix nerves.new to the panel; Linux Framebuffers has the details.

Any other panel (an SPI LCD, an e-ink controller) answers three questions in its own surface: how big it is, how its pixels are packed, and how bytes reach it.

defmodule MyDevice.Surface do
use RasterExRatatui.Surface, app: MyDevice.App, format: RasterExRatatui.PixelFormat.RGB565
@impl true
def init(_opts), do: {:ok, [size: {480, 320}], MyDevice.LCD.open!()}
@impl true
def push(patches, lcd) do
Enum.each(patches, &MyDevice.LCD.write(lcd, &1.x, &1.y, &1.width, &1.height, &1.data))
lcd
end
end

Input is whatever reads the hardware, turned into ExRatatui.Event structs and handed to RasterExRatatui.Surface.send_event/2. Building a Surface covers whole-frame panels, slow refreshes, crashes, rotation, and testing on the host. A device already driven from its own process starts a RasterExRatatui.Session there instead of a surface.

Examples

The examples folder has a headless snapshot script, a benchmark, a Nerves project for a Raspberry Pi display with keyboard and touch, and an e-ink name badge; its README says what each one shows and how to run it.

Guides

Guide Description
Nerves Quick Start From mix nerves.new to an app on a Raspberry Pi's display with keyboard and touch, the build traps, and first checks
Building a Surface The contract: geometry, push, input, crashes, rotation, testing, Session for an own process, and a 1-bit e-ink consumer
Fonts The glyph layout, the built-in 6×8 font, scale, and bringing a font
Pixel Formats Mono tone rules and dithering, colour palettes, writing a format
Linux Framebuffers Framebuffer.Surface and what it is made of: device geometry, writes, the kernel console, keyboards, and touch
Telemetry Rasterisation, push, and input events with a Telemetry.Metrics example

Ecosystem

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

MIT — see LICENSE.