Native Nim UI library based on the idea of "relays" -- dependency injection via global callbacks. Has Windows API, X11, Cocoa, GTK4, SDL2, SDL3 and FigDraw support. Write UI apps as easily as terminal apps!
focim, the Focussed Nim Editor, is written with it.
import uirelays is all you need -- it re-exports everything and
automatically initializes the native backend for the current platform
(WinAPI on Windows, Cocoa on macOS, X11 on Linux/BSD). Override with
-d:sdl3, -d:sdl2, -d:gtk4, or a FigDraw Nimble feature.
For finer control, import the submodules directly and call initBackend()
yourself:
import uirelays/[coords, screen, input, backend]
initBackend()Install this package with Nimble:
nimble installBackend selection:
- Default on Windows: WinAPI
- Default on macOS: Cocoa
- Default on Linux/BSD: X11
- Optional overrides:
-d:gtk4,-d:sdl3,-d:sdl2 - Optional features:
figDrawWindyandfigDrawSiwin
Choose either the Windy or siwin integration. The features are mutually exclusive and install only the selected windowing dependency:
atlas install --feature:uirelays.figDrawWindy
nim c --define:"features.uirelays.figDrawWindy" examples/hello.nim
atlas install --feature:uirelays.figDrawSiwin
nim c --define:"features.uirelays.figDrawSiwin" examples/hello.nimThe matching -d:figDrawWindy and -d:figDrawSiwin convenience aliases are
also accepted once their dependencies are available.
The SDL backends need extra Nim packages in addition to the native system libraries:
nimble install https://github.com/nim-lang/sdl3
nimble install https://github.com/nim-lang/sdl2Install sdl3 when building with -d:sdl3. Install sdl2 when building
with -d:sdl2.
Choose the backend you want and install its native development packages.
Default X11 backend:
sudo apt install libx11-dev libxft-devGTK4 backend:
sudo apt install libgtk-4-dev libpango1.0-dev libcairo2-dev libfontconfig1-dev libglib2.0-dev pkg-configSDL3 backend:
sudo apt install libsdl3-dev libsdl3-ttf-dev
nimble install https://github.com/nim-lang/sdl3SDL2 backend:
sudo apt install libsdl2-dev libsdl2-ttf-dev
nimble install https://github.com/nim-lang/sdl2Default X11 backend:
sudo dnf install libX11-devel libXft-develGTK4 backend:
sudo dnf install gtk4-devel pango-devel cairo-devel fontconfig-devel glib2-devel pkgconf-pkg-configSDL3 backend:
sudo dnf install SDL3-devel SDL3_ttf-devel
nimble install https://github.com/nim-lang/sdl3SDL2 backend:
sudo dnf install SDL2-devel SDL2_ttf-devel
nimble install https://github.com/nim-lang/sdl2The default Cocoa backend needs no extra native libraries.
nim c examples/hello.nimFor SDL backends on macOS, install the SDL libraries with your preferred package manager, then install the matching Nim package:
nimble install https://github.com/nim-lang/sdl3
nimble install https://github.com/nim-lang/sdl2The default WinAPI backend needs no extra native libraries.
nim c examples/hello.nimFor SDL backends on Windows, install the SDL native libraries separately and then install the matching Nim package:
nimble install https://github.com/nim-lang/sdl3
nimble install https://github.com/nim-lang/sdl2Use the default backend for your platform:
nim c examples/hello.nimForce a specific backend:
nim c -d:gtk4 examples/hello.nim
nim c -d:sdl3 examples/hello.nim
nim c -d:sdl2 examples/hello.nim
nim c --define:"features.uirelays.figDrawWindy" examples/hello.nim
nim c --define:"features.uirelays.figDrawSiwin" examples/hello.nim- focim -- the Focussed Nim Editor: a code editor with an integrated terminal, laid out and colored by an editable config file. It lived in this repository until it outgrew it; the widgets it is made of -- SynEdit, the terminal panel, the clipboard history -- went with it, since they were an editor's parts and not a UI library's.
- hello.nim -- Minimal window with text rendering
- paint.nim -- Simple drawing app with explicit submodule imports
- layout_demo.nim -- NIF layout system demo
- todo.nim -- Todo list app
The library is split into five relay groups:
| Module | Relays | Purpose |
|---|---|---|
screen |
windowRelays |
Window lifecycle, cursor, clip rect |
screen |
fontRelays |
Font loading, text measurement and rendering |
screen |
drawRelays |
Rectangles, lines, points, images |
input |
inputRelays |
Events, timing, shutdown |
input |
clipboardRelays |
Copy/paste |
Drivers populate these relay objects at init time. Application code calls
convenience wrappers (fillRect, drawText, waitEvent, ...) that
dispatch through the relays. No virtual calls, no inheritance, no heap
allocation -- just plain proc pointers.
| Driver | Platform | Dependencies |
|---|---|---|
winapi_driver |
Windows | None (GDI) |
cocoa_driver |
macOS | None (AppKit) |
x11_driver |
Linux/BSD | libX11, libXft |
gtk4_driver |
Linux/BSD | GTK4, Cairo, Pango |
sdl3_driver |
Cross-platform | SDL3, SDL3_ttf |
sdl2_driver |
Cross-platform | SDL2, SDL2_ttf |
figdraw_windy_driver |
Cross-platform | FigDraw, Windy |
figdraw_siwin_driver |
Cross-platform | FigDraw, siwin |
See Writing a custom driver for a guide on adding support for a new platform or graphics toolkit.
A window's boxes are described in NIF -- a
parenthesized format read by the dependency-free lexer in uirelays/tinynif
-- and uirelays/layout turns that description into a Rect per name:
(layout
(toolbar (lines 2))
(cols
(sidebar (px 250))
(editor))
(status (lines 1)))
(rows ...) stacks its children top to bottom, (cols ...) places them left
to right, and either may hold the other. Every other tag is a leaf that names
a widget and may state its size along the axis its parent divides: (px 250)
in logical pixels, (lines 5) in text lines, (stretch 2) in shares of what
is left over. resolve computes the rects; hitTest says which name a click
landed in. See layout_demo.nim.
The borders are draggable, and nothing has to be drawn for them to be: the
gap resolve leaves between adjacent boxes is already there, so it is the
grip. splitterAt says which border a pointer is on, dragTo moves it, and
$ writes the layout back out as the text it came from -- with the new sizes
in the unit each box was already written in, a (px N) staying pixels and a
(lines N) snapping to whole lines. An application that keeps its layout in a
file therefore gets mouse-resizable panels without keeping a second copy of
the sizes anywhere: what the pointer moves is what the file says.
The tree grows and shrinks the same way. splitCell puts a second box beside
one that is there -- to its right or below it -- taking the room out of that
box alone, halved in whatever unit it was written in, so no other border
moves; removeCell takes a box out again and folds away the container it
empties. cellNames says what is in the layout, which is how an application
works out which widgets it needs. Since a split is an edit of the tree and the
tree is the file, an application that spawns and closes panels stores nothing
about them: what the window holds is what the layout says, and one more cell
typed into the file by hand is one more panel.
MIT