RasterExRatatui

Hex.pmDocsCILicense

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.

ExRatatui app ──> ExRatatui.Server ──> CellSession diff (cells + regions)
RasterExRatatui.Raster (font × palette × pixel format × scale)
[%Patch{x, y, width, height, data}]
Surface.push/2 ──> the panel

Features

Quick start

The app needs no change: it is a plain use ExRatatui.App (or ExRatatui.run/2) that renders widgets exactly as it would in a terminal, so build and try it there first. Putting it on a panel is then one module, the surface, that answers three questions about the display: how big it is, how its pixels are packed, and how bytes reach it.

  1. Pick the format and scale.Mono for 1-bit panels, RGB565 or XRGB8888 for colour. scale: magnifies the built-in 6×8 font so the grid stays readable on a large panel: a 720×1280 display at scale 3 gives 40×53 cells.

  2. Write the surface.init/1 opens the device and returns its geometry, push/2 writes the rectangles that changed. On a Linux framebuffer (a Raspberry Pi with a display) the helpers do both:

    defmodule MyDevice.Surface do
    use RasterExRatatui.Surface, app: MyDevice.App, scale: 3
    alias RasterExRatatui.Framebuffer
    @impl true
    def init(_opts) do
    {:ok, fb} = Framebuffer.open("fb0")
    {:ok, format} = Framebuffer.format_for(fb.info)
    {:ok, [size: {fb.info.width, fb.info.height}, format: format], fb}
    end
    @impl true
    def push(patches, fb) do
    :ok = Framebuffer.write(fb, patches)
    fb
    end
    end

    A panel the kernel does not expose as a framebuffer (an SPI LCD, an e-ink controller) writes each patch with its own driver in push/2 instead.

  3. Supervise it and wire input. Add MyDevice.Surface to the supervision tree and the app is on the display. Whatever reads the hardware (a keyboard, GPIO buttons) turns its events into ExRatatui.Event structs and hands them over with RasterExRatatui.Surface.send_event/2; RasterExRatatui.Input.Evdev does the translation for evdev keyboards.

  4. Test on the host. A surface whose push/2 sends patches to the test process drives the real app without a device, and RasterExRatatui.Raster.frame/1 shows exactly what the panel would.

Building a Surface walks through each step, including panels that only take whole frames, slow refreshes, crashes, and resizing. Linux Framebuffers covers /dev/fb0, keeping the kernel console off the display, and keyboards. A device already driven from its own process (the name badge is one) can skip the surface and fold diffs with RasterExRatatui.Raster directly.

Examples

The examples catalog says what to look at in each.

Guides

GuideDescription
Building a SurfaceThe contract: geometry, push, input, crashes, testing, and the name badge as a worked example
FontsThe glyph layout, the built-in 6×8 font, scale, and bringing a font
Pixel FormatsMono tone rules and dithering, colour palettes, writing a format
Linux FramebuffersFramebuffer and Input.Evdev: device geometry, writes, the kernel console, and keyboards
TelemetryRasterisation, push, and input events with a Telemetry.Metrics example

Ecosystem

Installation

Add raster_ex_ratatui to the dependencies in mix.exs:

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

Prerequisites

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

MIT — see LICENSE.