{"slug": "retro-nim-ui-windows-3-1-style-ui-toolkit", "title": "Retro Nim UI – Windows 3.1 style UI toolkit", "summary": "Sultan Al Isaiee (@foxoman) released retroui, a Windows 3.1-style UI toolkit for the Nim language, installable via `nimble install retroui`, that builds apps for Windows, macOS, Linux, Android 7+, iOS and the web from one code base. A hello-world app compiles to a single 1.9 MB file (about 1 MB of it fonts) that runs in 7 MB of memory on one thread, versus 15-22 MB files and 15-120 MB of memory for the same Hello in egui, iced or Slint, and the toolkit requires no dependencies beyond Nim and a C compiler. The companion `retrokit` tool packages apps for Google Play (APK, App Bundle), the Microsoft Store (MSIX, Authenticode-signed program), the App Store (.ipa) and the web, and a Retro Designer tool edits the same `.nim` file directly.", "body_md": "**Developed by Sultan Al Isaiee [@foxoman](https://github.com/foxoman)** --\n[www.foxoman.net](https://www.foxoman.net) -- [sultan@foxoman.net](mailto:sultan@foxoman.net)\n\nCo-author: Claude (Anthropic)\n\nA **Windows 3.1 style UI toolkit for Nim** that builds business and desktop\napps for **Windows, macOS, Linux, Android, iOS and the web** from one code\nbase, and packs them for the stores itself. The\npackage and its module are called `retroui`: `nimble install retroui`, `import retroui`.\n\n**Learn the syntax: [docs/syntax.md](https://github.com/foxoman/retroui/blob/main/docs/syntax.md)** -- the whole way of\nwriting an app, every form of it as a small program with a picture of what\nit draws: what `row([50, -1])` does with the 50 and the -1, binding, events,\ncontainers, dialogs, how it is built and why it is easy to follow.\n\n**Documentation: [docs/controls.md](https://github.com/foxoman/retroui/blob/main/docs/controls.md)** -- every control with\npictures: getting started, layout, each control's example and API, the looks.\n\n**Design it: [Retro Designer](https://github.com/foxoman/retroui/blob/main/docs/designer.md)** -- `retrokit designer app.nim` shows your file's windows drawn with the real controls: drop\ncontrols, drag them, set their properties, add their events, and every\nchange is written into the same `.nim` file.\n\n- **Small and light.** A \"hello\" app is a single 1.9 MB file (Linux\nx86-64, built with`retrokit small` ; about 1 MB of it the fonts) that\nruns in 7 MB of memory on one thread -- the same Hello in egui, iced or\nSlint is a 15-22 MB file in 15-120 MB (see[Speed and size](#speed-and-size) );\nthe full file manager is 4.3 MB, and the gallery with every control, the\nstreet map, 3D models, the canvas and PDF 5.7 MB. Fonts, icons, the PDF writer, the world map, the 3D view and\nthe decoders are all built in, and only what an app calls is linked.\n- **No dependencies.** You need Nim and a C compiler — nothing else to\ninstall, now or on the user's machine: no GTK, Qt, SDL, Electron, browser\nengine or Java toolchain. On Linux, libX11 is the one library an app\nneeds, loaded when it starts (Xft, XRender, EGL and the rest are used when\nthe system has them), so building needs no`-dev` packages; sound, video\nand printing use whatever the system offers, loaded only when used.\n- **Runs anywhere.** One code base for Windows, macOS, Linux/BSD (X11, and\nWayland with a driver of its own), Termux:X11, Android 7+, iOS, a web page\n(WebAssembly made by clang alone, no Emscripten) and a screen with no\nwindowing system at all (a kiosk, a device: the Linux framebuffer or DRM).\nTouch, mouse and keyboard all work everywhere, and the layout adapts to\nphones, tablets, laptops and 4K screens.\n- **Ships itself.**`retrokit` makes what each store takes, in Nim, on any\nsystem: an APK and an App Bundle for Google Play, an MSIX and an\nAuthenticode-signed program for Windows and the Microsoft Store, a signed\nmacOS app (notarized on a Mac), an .ipa for the App Store, a web page\nthat installs and works offline.\n- **Fast.** Immediate mode with a software renderer that redraws only the\n32-pixel bands that changed: a full 800×600 window takes about 0.25 ms, an\nunchanged frame costs nothing and is not sent to the screen, and the app\nuses no CPU while it waits. A 50,000-file folder or a 100,000-row grid\nscrolls in well under a millisecond per frame.\n- **For everyone.** Screen readers read every control and act through it\non every platform -- Orca, NVDA, JAWS, Narrator, VoiceOver, TalkBack --\nwith nothing for an app to do but put a label before a box; input\nmethods (Chinese, Japanese, Korean) compose in the text box; the window\ntakes the scale of the monitor it is on, 125 % or 175 % alike.\n- **Reaches any OS API.**`retroui/native` calls any Android Java API without\nwriting Java, any macOS/iOS API through the Objective-C runtime, Win32 and\nCOM, and any C library through`dlopen` , with ready-made cross-platform\ncalls (battery, vibrate, dark mode, device name…).\n- **Business ready.** Data grids with check boxes, colour status cells,\ndrop-downs, buttons, per-cell colours and totals; forms with masked\nfields; record navigators; property grids; tree grids; 21 chart types;\nPDF reports from HTML templates, with printing; kanban, Gantt, agenda and\norg charts; maps; MDI windows; wizards.\n- **Looks the part.** The 3.1 look down to the pixel — black outlines, the\ntwo-step bevels, the default button's ring, navy and white title bars,\ndithered scroll tracks, etched group boxes, hard menu shadows — in**FreeSans** (GNU FreeFont's Helvetica-like sans, with its bold, oblique\nand bold oblique), its heights on whole pixels so it is crisp at 100 %,\nat 125 % and 150 %, and on HD, 2K and 4K screens, with Arabic everywhere\n(FreeSerif's).**FreeSerif** ,**FreeMono** or a font file are the other\nchoices (`setUiFont` , or the user in`appearanceSettings` ; see \"The\nfonts\" below). 255 pixel icons in\nthe manner of the period. Lists and pages glide when scrolled, and a\nflicked finger keeps them moving.\n- **Safe.** Every decoder and parser (pictures, zip and ZIP64, CSV,\nMarkdown, HTML, OBJ, STL and glTF models, vector tiles, the map services'\nanswers, certificates, the programs it signs) is fuzzed with hostile\ninput, and the tests also run under AddressSanitizer and as 32-bit\nprograms. See[SECURITY.md](https://github.com/foxoman/retroui/blob/main/SECURITY.md) .\n\n**Every control, its API and how it looks in each state** (normal, keyboard\nfocus, mouse over, pressed, disabled, open, touch screen, dark scheme) is in\n**[docs/controls.md](https://github.com/foxoman/retroui/blob/main/docs/controls.md)**; the generated API reference of\nevery module is in `docs/api/` (open `docs/api/retro.html`).\n\n[The syntax, studied](https://github.com/foxoman/retroui/blob/main/docs/syntax.md) · [Install](#install) · [Your first app](#your-first-app) ·\n[Retro Designer](#retro-designer) · [How it works](#how-it-works) · [Controls](#controls) · [Layout](#layout) ·\n[Text and icons](#text-and-icons) ·\n[Screen readers and input methods](#screen-readers-and-input-methods) · [Business apps](#business-apps) ·\n[Charts](#charts) · [Reports, PDF and printing](#reports-pdf-and-printing) ·\n[Planning views](#planning-views) · [Desktop apps](#desktop-apps) ·\n[Files](#files) · [Maps](#maps) · [3D](#3d-view) · [Canvas](#canvas) · [Whiteboard](#whiteboard) ·\n[What the computer is doing](#what-the-computer-is-doing) ·\n[Image editing](#image-editing) · [Web and HTML](#web-and-html) · [Sound and video](#sound-and-video) ·\n[Camera and microphone](#camera-and-microphone) · [More windows](#more-windows) ·\n[The system's features](#the-systems-features) · [The system's own APIs](#the-systems-own-apis) ·\n[Updates inside the app](#updates-inside-the-app) · [Platform services](#platform-services) · [Android](#android) · [iOS](#ios) ·\n[Apps as web pages](#apps-as-web-pages) · [Screens without a desktop](#screens-without-a-desktop) ·\n[Shipping to the stores](#shipping-to-the-stores) ·\n[Platforms](#platforms) · [Speed and size](#speed-and-size) ·\n[Safety](#safety) · [Tests](#tests) · [Examples](#examples) · [FAQ](#faq) ·\n[Limits](#limits) · [License](#license)\n\nYou need **Nim 2.0 or newer** and **a C compiler**:\n\n| System | Get them | Notes | \n|---|---|---|\n| Windows | `choosenim` (brings MinGW-w64) | GDI, nothing else | \n| macOS | `brew install nim` ,`xcode-select --install` | Cocoa | \n| Linux / BSD | your package manager: `nim` ,`gcc` or`clang` | X11 is loaded at run time, Wayland spoken by the kit itself: no `-dev` packages | \n| Android, on the device | Termux: `pkg install nim clang ndk-sysroot git` | builds APKs right on the phone | \n| Android, from a desktop | a clang with lld (LLVM on Windows, `brew install llvm lld` ,`clang lld` packages) or zig; with neither, the first APK build fetches the NDK into`~/.retro/ndk` | no NDK, no Java, no Gradle, no SDK, no OpenSSL | \n| iOS | a Mac with Xcode |  | \n| The web | clang and lld ( `apt install clang lld` , LLVM on Windows,`brew install llvm lld` ) | WebAssembly without Emscripten | \n\nThen:\n\n```\nnimble install retroui          # the library and its tools (or the repository's URL)\nretrokit doctor                                # checks what this machine has\nretrokit new myapp --kind:business             # a ready app: hello, form, business, files, charts\ncd myapp && nimble run\n```\n\n`retrokit` is the helper that comes with the package:\n\n| Command | What it does | \n|---|---|\n| `retrokit new NAME --kind:K` | a folder with `NAME.nim` ,`NAME.nimble` (tasks run, small, apk) and an icon | \n| `retrokit doctor` | checks Nim, the C compiler, the screen libraries, the Android tools, what Retro Designer needs, and says how to fix what is missing | \n| `retrokit run app.nim` | builds in release mode and runs it | \n| `retrokit small app.nim` | the smallest build (optimized for size, stripped), and its size | \n| `retrokit apk app.nim` | an Android APK (the `retroapk` tool, below) | \n| `retrokit aab app.nim` | an Android App Bundle for Google Play, signed with your upload key | \n| `retrokit web app.nim` | the app as a web page: installable, offline ( [Apps as web pages](#apps-as-web-pages) );`retrokit run --web --watch` serves it and reloads on every save | \n| `retrokit sign app.exe` | Windows' Authenticode signature, made here, with a time stamp | \n| `retrokit msix app.exe` | an MSIX for the Microsoft Store and Windows' installer, signed | \n| `retrokit mac app` | a macOS `.app` , signed ad hoc here or with your Developer ID, notarized and stapled on a Mac | \n| `retrokit ipa App.app` | an iOS app as the `.ipa` TestFlight and the App Store take | \n| `retrokit model car.blend` | a 3D model (Blender's, glTF, OBJ, STL) as the `.glb` an app carries, its parts and clips named | \n| `retrokit designer app.nim` | [Retro Designer](#retro-designer) : the file's windows to design, every change written into the file (built once for this computer) | \n\nFrom a clone of this repository (the examples are built only when you ask,\none at a time; `nimble install retroui` installs only the toolkit and its\ntools):\n\n```\nnimble gallery        # every control in one window\nnimble files          # Retro Files, the file manager (a folder after it: opens there)\nnimble ledger         # a small business app: invoices, customers, reports\nnimble invoice        # an invoice from an HTML template: preview, PDF, printing\nnimble charts         # Chart Viewer: a CSV file as charts, saved as PNG\nnimble photo          # Retro Photo, the image editor\nnimble board          # Retro Board, the whiteboard\nnimble taskman        # Retro Task Manager: the programs, the processor, the memory, the network\nnimble mail           # Retro Mail: Microsoft 365, Outlook.com, Gmail, any IMAP (mailexe, mailapk)\nnimble hrbook         # HR Book: employee records of several hotels, sealed with a PIN\nnimble apk            # Retro Files as an Android APK (build/RetroFiles.apk)\nnimble photoapk       # Retro Photo as an APK (photoinstall: build and adb install)\nnimble galleryapk     # the gallery as an APK\nnimble boardapk       # Retro Board as an APK (boardinstall: build and adb install)\nnimble taskmanapk     # Retro Task Manager as an APK (taskmanexe: for Windows)\nnimble photoexe       # Retro Photo for Windows, with its icon (from Linux or macOS: MinGW; build/win/)\nnimble filesexe       # Retro Files for Windows (galleryexe, boardexe: the others)\nnimble photoappimage  # Retro Photo as a Linux AppImage, with its icon and menu entry (one file, runs anywhere:\nnimble filesappimage  # Retro Files as an AppImage      built against glibc 2.31 with zig when it is there)\nRETRO_APP=hrbook nimble exe        # any one app of the table in retroui.nimble: exe, appapk,\nRETRO_APP=hrbook nimble appapk     #   appimage, macapp\nnimble apks           # every app as an APK            (the apps: the table in retroui.nimble)\nnimble exes           # every app for Windows\nnimble appimages      # every app as an AppImage\nnimble macapps        # every app for macOS, zipped .app (on a Mac: build/mac/)\nnimble apps           # every example for this computer, into build/\nnimble dist           # the apps and all the APKs, into build/\nnimble test           # the headless tests\nnimble fuzz           # the fuzzers\nnimble docs           # the API reference (docs/api) and docs/controls.md\n```\n\n**Step by step** -- an APK from Windows, macOS, Linux or a BSD without\nthe Android SDK, an iOS app on a Mac, signing and installing: see\n[BUILDING.md](https://github.com/foxoman/retroui/blob/main/BUILDING.md).\n\nEvery task builds with what `nimble install` brought: the APK tasks need\nno Android NDK -- `retroapk` carries Android's headers and libraries (1.2 MB,\nunpacked once into `~/.retro/android-sysroot`) and builds with a clang that\nhas lld, or zig; with neither they fetch the NDK once (about 700 MB, into\n`~/.retro/ndk`; `nimble ndk` does it now, `ANDROID_NDK_HOME` uses yours), sign with a key made for you in\n`~/.retro/android` (`RETRO_KEYS` another folder), and need no Java, Gradle, SDK, OpenSSL or apksigner. In\nTermux the compiler is installed with `pkg` the first time.\n\nGitHub builds them too: **CI** runs the tests on each push and compiles\nthe tools on Linux, Windows and macOS (the examples when run by hand);\n**Release** -- a `v...` tag pushed, or by hand -- makes the apps I use\nevery day, Retro Gallery, Retro Files, Retro Photo, Retro Board and Retro\nMail, as an AppImage, a Windows .exe, an APK and a macOS .app, and puts\nthem on the GitHub release of that tag. Its APKs are signed with the key in\nthe secrets `ANDROID_KEY_PEM` and `ANDROID_CERT_DER` (base64 of the files in\n`~/.retro/android`), so a new one installs over the one before.\n\nA desktop app has the system's own frame, or the one Windows 3.1 had: the\ngray sizing border with dark lines at its corners, the navy title bar (white\nwhile another window is in front) with the control-menu box at its left and\nthe minimize and maximize buttons at its right. The app draws that one\nitself, in a window without the system's frame, and the system still does\nthe rest: drag the title bar to move the window (Aero Snap on Windows), the\nborder or a corner to size it; a double click or the maximize button\nmaximizes it, down to the taskbar; Alt+Space opens the control menu\n(Restore, Move, Size, Minimize, Maximize, Close, the UI's size). The menu bar,\ntool bars and the content move down under it as under a phone's title bar,\nso an app needs no change. A **size grip** sits in the bottom right corner\ninside the frame (in the status bar's end when there is one; the bottom\nleft, right to left): drag it to size the window, with the mouse or a\nfinger, on every platform the 3.1 frame is drawn. The frame takes the\ntheme's colors, and follows when the user picks another theme.\n\n**A window that keeps its size:** `app \"Calculator\", 260, 320, resizable = false:`\n(or `setResizable(false)`). The 3.1 frame then has no sizing border to drag,\nno maximize button and no grip (its border is the plain band 3.1's dialogs\nhad), and the system's frame is told the same: Windows drops the sizing\nborder and the maximize box, macOS the resize edges and the zoom button,\nX11 window managers get the same least and largest size, GTK 4 and SDL\nmake the window fixed.\n\n```\nRETRO_FRAME=classic nimble mailexe    # Retro Mail for Windows with the 3.1 frame\nRETRO_FRAME=classic nimble apps       # every example (exes and appimages the same way)\nnim c -d:retroFrame=classic app.nim   # by hand (system: the default)\nRETRO_FRAME=system ./app              # when it starts: the system's frame after all\n```\n\n**The user can always switch.** In every app, with nothing to write: the\n3.1 frame's control menu has **Windows' Own Frame** (elsewhere **Desktop's\nOwn Frame**); on Windows the system's frame gets **Windows 3.1 Frame** in\nits system menu (the title bar's icon, Alt+Space), and on macOS the menu\nbar gets it in a **Window** menu (checked while that frame is on). The\nchoice is kept for the app (`window.ini` in its data folder) and wins over\nthe frame it was built with at the next start; `appearanceSettings` offers\nit too, as **Window**. On X11 and Wayland the window manager keeps its menu\n(Alt+Space, a right click on the title bar) to itself: there\n`appearanceSettings` is the way back to the 3.1 frame, so offer it.\n\n**Where:** every desktop. Windows; macOS; Linux and the BSDs on X11, with\nany window manager (EWMH: GNOME, KDE, Xfce, openbox... -- snapping and\ntiling as for other windows) or none (an X server that shows one app: the\napp's window is then moved, sized and maximized by the driver); Wayland\ndesktops with the kit's own Wayland driver (the default build takes it when\nthere is no X11, `RETRO_DRIVER=wayland` asks for it: the system's frame\nwhere the compositor draws one, else the 3.1 frame by itself), through\nXWayland, or with `-d:gtk4` or `-d:sdl3`. The other drivers have it too: GTK 4, SDL 3\nand SDL 2 (where a double click on the title bar maximizes on Windows only,\nand the control menu's Move and Size do nothing), Siwin (not on macOS; on\nWindows the frame chosen is the next window's). Only the phones keep their\nown.\n\n**For the developer:** `app`'s `frame` (`wfAsBuilt`, `wfSystem`,\n`wfClassic`) is the frame the window opens with, `userFrame = false` takes\nthe user's choice away; `setClassicFrame(on)` changes it while the app runs,\n`chooseClassicFrame(on)` changes it as the user would (and keeps it),\n`classicFrame()` says which one is on. A window manager's own keys stay its\nown: openbox, say, opens its window menu for Alt+Space before the app sees\nit (the control-menu box still opens the app's).\n\n``` python\nimport retroui\n\nvar name = \"World\"\nvar greeting = \"\"\n\napp \"Hello\", 320, 120:\n  row([50, -1]):\n    label \"&Name:\"\n    edit name\n  row:\n    if button(\"&Greet\", isDefault = true):\n      greeting = \"Hello, \" & name & \"!\"\n    if button(\"E&xit\"): quitApp()\n  label greeting\nnim c -r -d:release -d:useMalloc hello.nim   # or: retrokit run hello.nim\nretrokit small hello.nim          # optimized for size, LTO, stripped: about 1.9 MB, one file\nretroapk hello.nim --install      # on a phone in Termux\n```\n\nIn Termux the same `nim c -r` runs it in a window on Termux:X11 (see\nBUILDING.md, \"A desktop app in Termux\").\n\n**Working on an app in Termux.** Without a window manager, Termux:X11 gives\nan app the whole screen, as Android does. To see it as a desktop window\nwhile you work on it:\n\n```\nretrokit run --window app.nim              # its own size, in its 3.1 frame, in the middle\nretrokit run --live app.nim                # the same, built again and shown again on every save\nretrokit run --window --scale:125 app.nim  # everything (and the window) a quarter larger\nretrokit run --window --theme:Dark --size:420x840 app.nim\n```\n\nThe toolkit draws a desktop (the 3.1 teal) over the screen and puts the\nwindow on it at the size the app asked for, grown by the screen's own scale\n(its density, so a 2K tablet shows it at its real size), with the title bar\nto move it, the border and the grip to size it, the maximize button and the\ncontrol menu. Keys go to the window, also after a tap on the desktop. When\nthe screen changes size (the tablet turns, Termux:X11's window is made\nfloating or sized) the desktop follows, and the window stays in view: made\nsmaller while the screen is, its size again when there is room. The\nline it prints says what it came to (`RETRO_WINDOW: 660 x 304 pixels on a 2560 x 1600 screen; the screen's scale 200 %`); `--scale:N` makes it\nlarger or smaller. `retrokit run` passes anything else to the compiler.\n\n**Live: see each change as you save**, on any desktop. `retrokit run --watch app.nim` builds the app again whenever a `.nim` file in its folder (or two\nlevels below) is saved, starts the new one where the window was -- the place\nand the size you gave it -- and then closes the one before, so the window\nnever goes blank. It is the same on Windows, macOS, Linux (any window\nmanager, or none) and Termux, where `--live` is `--watch` with `--window`.\nOnly what changed is compiled again: a few seconds, most of it Nim reading\nthe kit. Code that does not build leaves the window running what it had,\nwith the compiler's message in the terminal; an app that crashes starts\nagain with the next save. The app starts fresh each time (its variables are\nnew); closing its window, or Ctrl+C, ends it. The window's place is kept by\nthe X11, Windows, macOS, SDL 2 and SDL 3 drivers (`RETRO_WINDOW_STATE`,\n`keptWindowPlace`); GTK 4 and Siwin cannot place a window, so theirs opens\nwhere the system puts it.\n\n```\nretrokit run --watch app.nim              # Windows, macOS, a Linux desktop\nretrokit run --live app.nim               # Termux:X11: --window and --watch\n```\n\nOn Windows from Linux: `nim c -d:release -d:useMalloc -d:lto --opt:size --passL:-s -d:mingw --cpu:amd64 --app:gui hello.nim`.\n\nBuild with `-d:useMalloc` (or put `switch(\"define\", \"useMalloc\")` in your\n`config.nims`; `retroapk` adds it by itself): with Nim's own allocator a\nprogram never gives memory back to the system, and one that shows many\npictures only grows. The toolkit warns at compile time without it, and\n`retroui/memwatch` gives back what the program lets go of\n(`trimHeap`, by itself when it rests and when it goes to the background).\n\n`app` opens the window and runs its body once per frame. Each widget\ndraws itself and returns what happened: `button` is `true` when it is\nclicked, and `edit name` edits `name` in place and returns `true` when it\nchanges.\n\n`app` takes options by name after the size, all optional:\n\n``` js\nconst ledgerIcon = staticRead(\"ledger.png\")\n\napp \"Ledger\", 900, 600,\n    theme = Dark,                    # the colors (builtinThemes, or your own Theme)\n    frame = wfClassic,               # wfAsBuilt (default), wfSystem, wfClassic\n    userFrame = true,                # the user may switch it (kept per app)\n    gpu = gmAuto,                    # gmAuto, gmGpu, gmCpu\n    scale = 125,                     # the size of everything, in percent\n    font = ufSans,                   # the UI's font (ufSerif, ufMono)\n    zoom = 0,                        # whole-pixel magnification (0: the display's)\n    id = \"ledger\",                   # the app's name for what the toolkit keeps\n    icon = ledgerIcon,               # the window's picture (a PNG's bytes)\n    resizable = true,                # false: the window keeps its size\n    look = \"ledger\":                 # keep the look the user picks (appearanceSettings)\n  label \"Hello\"\n```\n\nA misspelled option is a compile error that lists them all. $RETRO_FRAME, $RETRO_GPU, $RETRO_SCALE, $RETRO_THEME, $RETRO_SIZE and $RETRO_ZOOM win over the options when the app starts (tests, screenshots); $RETRO_SCALE makes the window larger with everything in it. $RETRO_WINDOW=1 opens the app as a window in its 3.1 frame, in the middle of a desktop the toolkit draws, where the screen has no window manager (see \"Working on an app in Termux\" below).\n\n`retrokit designer app.nim` opens your `.nim` file in **Retro Designer**, the\nkit's visual designer, in the manner of Qt Designer: its windows drawn with\nthe real controls, the Widget Box with every control of the kit, the Object\nTree, the properties, events and variables of what you choose. Drop and drag\ncontrols, put them in rows and groups, set their properties, add their\nevents -- each change is written into the same file as Nim, touching only\nthe lines that changed: no form file, no generated code. It is purely a\ndesigner: **Edit Code** opens your editor at the control's code, and while\none of them has the file the other leaves it alone (a lock beside it, the\nfile read-only for the others). Dialogs and menus open over their window\nwhen you choose something in them; F5 shows the window working, in each\nlook.\n\nIt comes with the package and builds itself the first time you run it, for\nyour computer and your Nim: Windows, macOS, Linux and BSD, and Termux on\nAndroid (Termux:X11). The whole guide: **[docs/designer.md](https://github.com/foxoman/retroui/blob/main/docs/designer.md)**.\n\n```\nretrokit designer app.nim       # app.nim's windows (a new file when there is none)\n```\n\n- **Immediate mode.** The UI is plain code that runs every frame, and\nwidgets are procs bound to your own variables. There are no callbacks\nand no widget objects to create, keep or free; whatever your code draws\nis what the screen shows. A widget is known by the variable it is bound\nto or by its label, so you never manage ids (use`pushId` /`withId` for\nrows of the same widgets).\n- **Frames only when needed.** A frame runs when input arrives, when a\ntimer asked for one (`wakeIn(ms)` ) or when work on a background thread\nfinished. Between frames the app sleeps. Drawing commands carry hashes:\nan unchanged frame is skipped, and a changed one redraws only the bands\nthat changed. A minimized window draws nothing at all (`ui.hidden` :\nits frames and timers go on, so it can read less often).\n- **Input, the same everywhere.** Mouse: click, double-click, the wheel\n(Shift for sideways, Ctrl to zoom where it makes sense), drag, the right\nbutton for menus, the middle and side buttons. Keyboard: Tab and\nShift+Tab move the focus (shown as the dotted 3.1 rectangle), Space and\nEnter activate, arrows/Home/End/PageUp/PageDown move, Esc closes,\nAlt+letter for`&` mnemonics, the menu key or Shift+F10 for context\nmenus. Fingers: a tap activates (a drag cancels it), dragging scrolls\nlists and pages, a long press is the right click, and a held-then-moved\nfinger drags items. Hover effects appear only for a real mouse.\n- **Looks.** The 3.1 look (bevels, X check boxes, navy selections, group\nboxes, bold menus) in eight colour schemes (`builtinThemes` : Classic,\nGray, Dark, Ocean, Desert, Plum, Hot Dog, Paper), or your own`Theme` (`ui.theme = Dark` ).\nText is FreeSans (the default), FreeSerif or FreeMono -- GNU FreeFont,\nbuilt in, each in its four styles, Arabic everywhere -- or a font file\n(`setUiFont` ,`loadUiFontFile` ); icons are\nthe 3.1-style pixel icons or the Nerd Fonts symbols.\n- **Sizes.** Layout uses design pixels that follow the screen's density\n(Xft.dpi, GDK_SCALE, the monitor's real size, Android's density, Windows\nper-monitor DPI), in whole pixels so lines stay sharp. Users can make\neverything larger (Ctrl + / − / 0,`setUiScale` ) or just the text\n(`setTextScale` ). Touch screens get finger-sized metrics.\n- **Content larger than its place scrolls** , always with the standard 3.1\nscroll bar (on touch screens too, where a finger can also drag the\ncontent): the window's page (both ways), dialogs, panes, lists, grids,\nMDI windows, the details pane.\n\nThe full catalogue, with every signature, a usage example and pictures of\neach state, is **[docs/controls.md](https://github.com/foxoman/retroui/blob/main/docs/controls.md)**. In short:\n\n| Group | Controls | \n|---|---|\n| Text | `label` ,`paragraph` ,`heading` ,`styledText` ,`link` ,`badge` ,`icon` ,`iconLabel` ,`tooltip` | \n| Buttons | `button` (also with a picture: icon only, text only, text below or beside the icon --`ButtonLook` ),`barButton` (tool bars;`setToolbarLook` ),`buttonAt` ,`toolButton` ,`menuButton` ,`splitButton` ,`segmented` ,`buttonRow` | \n| Choices | `checkbox` ,`radio` ,`toggle` (3.1 slide switch),`chips` ,`tagInput` ,`rating` ,`knob` | \n| Text input | `edit` ,`editArea` ,`searchBox` ,`autocomplete` ,`maskedEdit` ,`comboEdit` ,`hotkeyEdit` | \n| Numbers, dates, colours | `spin` ,`intEdit` ,`numberEdit` ,`slider` ,`rangeSlider` ,`progress` ,`datePicker` ,`calendar` ,`dateRange` ,`timePicker` ,`colorPicker` | \n| Lists | `listbox` ,`combo` ,`checkList` ,`grid` (with cell kinds),`tree` ,`treeGrid` ,`iconView` ,`lineView` ,`dualList` ,`propertyGrid` | \n| Status | `progressRing` ,`spinner` ,`sparkline` ,`usageBar` ,`banner` ,`toast` ,`led` ,`pill` ,`timeline` ,`emptyState` ,`busyState` ,`statusbar` | \n| Containers | `group` ,`card` ,`tabs` ,`tabStrip` ,`expander` ,`accordionHeader` ,`form` /`field` ,`split` ,`vsplit` ,`sidePane` ,`scrollPanel` ,`adaptive` ,`row` ,`col` | \n| Menus and bars | `menubar` ,`menu` ,`menuItem` ,`submenu` ,`contextMenu` ,`popupMenu` ,`toolbar` ,`statusbar` ,`breadcrumb` ,`pathBar` ,`driveBar` ,`pager` ,`stepper` ,`recordNav` ,`findBar` | \n| Dialogs | `dialog` ,`msgBox` ,`inputBox` ,`progressBox` ,`wizard` ,`aboutBox` ,`fileDialog` (3.1),`filePicker` (modern),`propertiesDialog` | \n| Desktop | `mdiArea` /`mdiWindow` ,`mdiWindowMenu` ,`hotkeyPressed` ,`findIn` ,`replaceAllIn` | \n| App parts | `settingToggle` ,`settingChoice` ,`settingSlider` ,`settingButtons` ,`settingCombo` ,`settingLink` ,`appearanceSettings` ,`avatar` ,`factList` ,`bottomNav` ,`fab` ,`captionBar` | \n| Business views | `chart` (21 kinds),`gauge` ,`kanbanBoard` ,`ganttView` ,`agendaView` ,`kpiTile` ,`statCard` ,`orgChart` ,`reportView` | \n| Files | `fileList` ,`filePreview` ,`folderTree` ,`placesView` ,`trashList` ,`driveBar` ,`pathBar` ,`textFileView` ,`hexFileView` ,`runJobDialog` | \n| Media and more | `mapView` ,`view3d` ,`canvas2d` ,`htmlView` ,`webView` ,`markdownView` ,`imageView` ,`animation` ,`videoView` | \n\nButtons with a picture show it four ways -- icon only (the label becomes the\ntooltip), text only, the text below the icon, or beside it -- and a tool bar\nshows all of its `barButton` s one way, which the app can offer as a setting\n(Explorer 98's \"Text Labels\"):\n\n```\nsetToolbarLook(blTextBelow)            # labels under the icons; the bar grows\ntoolbar:\n  if barButton(pxBack, \"Back\"): goBack()\n  if barButton(pxCopy, \"Copy\", tip = \"Copy (Ctrl+C)\"): copy()\nif button(\"Open\", pxOpen, blTextBeside): openIt()\nif button(\"Save\", glyphPic(nfSave), blIconOnly): saveIt()\n```\n\nDrawing your own: `canvas(w, h)` gives a rectangle to draw into with\n`fill`, `box`, `hline`, `vline`, `line`, `text`, `image`, `drawBitmap`\nand the smooth shapes `disc`, `ring`, `roundRect`, `polygon`,\n`thickLine`, `pieSlice`; `canvas2d` adds the whole HTML5 Canvas API.\n\n```\nrow([120, -1, -2]):             # fixed, one share, two shares\n  label \"Name:\"\n  edit name\n  edit notes\nrow:\n  label \"Total: 120\"\n  spring()                       # the buttons go to the right end\n  button \"OK\"\n  button \"Cancel\"\nadaptive(260):                   # 1 column on a phone, 3 on a desktop\n  card \"Sales\": ...\n  card \"Stock\": ...\n  card \"Orders\": ...\nif isCompact(): ...              # sizeClass(): scCompact, scMedium, scExpanded\nlet cols = responsive(1, 2, 3)\n```\n\nWidths in rows, `col(w)`, dialogs and grid columns are design pixels:\nthey grow with the screen and the user's size. `dp(13)` scales your own.\nContent larger than the window scrolls both ways under the fixed menu,\ntool and status bars, always with the standard scroll bars (on touch\nscreens too): the wheel (Shift + the wheel goes sideways), a finger,\nPageUp / PageDown, Ctrl+Home / End, and Tab brings the focused control\ninto view. A dialog taller than the screen scrolls inside; `scrollPanel`\nscrolls a part of a page (`scrollPanel -1:` down to the bottom).\n\n**Smooth scrolling.** Lists, grids, trees, text areas and pages glide a\nshort way per wheel notch or key, follow a finger pixel for pixel and fly\non when it is flicked; a finger landing on a flying list stops it without\npicking an item. `setSmoothScrolling(false)` (or the user, in\n`appearanceSettings`) makes every scroll jump at once. Views of your own\nget the same with `scrollWheel`, `scrollPan`, `scrollTo` / `scrollBy` /\n`scrollShow` and one `scrollTick` a frame.\n\n**Sizes.** The UI follows the display's density: whole pixels keep lines\nsharp (2x, 3x) and the text and controls make up the rest (a 1.33x tablet\ngets 1x lines and 1.33x text). `setUiScale(125)` (and Ctrl + / - / 0, and\nthe phone title bar's menu) scales everything; `setTextScale(120)` only\nthe text (controls grow with it); `setBaseTextSize(14)` changes the 100%\nsize (12 pixels per em on desktops and tablets, 15 on phones).\n[docs/syntax.md](https://github.com/foxoman/retroui/blob/main/docs/syntax.md#widths-in-a-row) shows each kind of width\nwith the pixels it came to. Try layouts on a desktop with `RETRO_SIZE=420x840`, `RETRO_TOUCH=1`,\n`RETRO_CAPTION=1` (the phone title bar), `RETRO_SCALE=150`, `RETRO_ZOOM=2`.\n\n**Controls for a mouse or for fingers.** `setControlSizes` chooses:\n`csAuto` (finger-sized on a phone, desktop-sized on a tablet and a desktop,\nwhose screens hold a desktop layout), `csDesktop` or `csTouch` (finger-sized\neverywhere). The user chooses in Appearance (`appearanceSettings`, Controls),\nkept with the look. `RETRO_TOUCH=1` is a finger on any screen: finger-sized\ncontrols, no hover, tooltips above the finger; `RETRO_CONTROLS=touch`,\n`desktop` or `auto` sets the sizes alone (`retrokit run --touch`,\n`--controls:touch`). Both win over what the user kept.\n\n```\ntext(x, y, \"Plain\", c)\ntext(x, y, \"Big bold\", c, style(bold = true, size = 2.0))\ntext(x, y, \"مرحبا بالعالم\", c)                  # joined, right to left\nlabel nfFolderOpen & \" Documents\"              # an icon inside text\ntoolButton(nfCopy, \"Copy\", size = 1.3)\nimage(x, y, glyphIcon(nfFolder, color(222, 170, 40), 24))   # as an Icon\n```\n\n`TextStyle` has `bold`, `italic` (the font's own italic: FreeSans Oblique,\nnot a slanted copy), `mono` (FreeMono), `serif` (FreeSerif), `underline`,\n`strike`, `size` (times the UI's text size) and `rtl`. `textWidth`, `lineHeight` and\n`FontH` measure. The icon constants (`nfFolder`, `nfCog`, `nfGithub`...,\n`nerdIconNames` lists them all) come from Font Awesome, Material Design,\nSeti and Devicons via Nerd Fonts. Characters in no built-in font (CJK,\nemoji) are drawn by the platform.\n\n**Drawing.** Apps draw into one software framebuffer cut in tiles: only\nthe tiles whose commands changed are drawn (on all cores) and sent, and a\npicture changed in part (`changed(bitmap, rect)`) only there; nothing at\nall when nothing changed. The framebuffer is the window's own memory where\nthe system lends it -- shared with the X server (MIT-SHM) on X11, a DIB\nsection on Windows -- so a frame is never copied on its way to the screen\n(`RETRO_SURFACE=0` copies it, as before 0.26). Where the platform has OpenGL ES 2 / OpenGL 2.1\n(EGL on Linux and Android, WGL on Windows -- loaded at run time, nothing\nto link) the frames are drawn by the GPU instead: the same commands as\nrectangles from textures the CPU makes once (text, icons, shapes),\npictures as textures sent only where they changed. A start-up self-test,\na fall back to the CPU and a guard against a crash at start keep a bad\ndriver from hurting; `setGpuMode(gmAuto/gmGpu/gmCpu)` or \"Drawing\" in\n`appearanceSettings` chooses. The text is drawn at the screen's own\nresolution on every screen; on HD and 4K screens pixel icons are scaled\nwith Scale2x and Scale3x, and small marks are drawn at the screen's\nresolution (`setHdText(false)`: the pixels of 1998, magnified). There is a true-black OLED theme; in it (and in Dark) the pixel icons are\ndrawn as their line twins -- Nerd Fonts symbols in the text's color, crisp\nat any size (`setIconStyle`).\n\n**Big images.** The photo engine (`retroui/photo`) shows a change being made\nwith the mouse -- a layer moved, transformed, adjusted -- before it is done\n(`LivePart`): the view's pixels are composited straight from the layers at\nthe view's size, and the full-size pixels change once, when it is put down\n(`touchWhole` hands the old ones to the undo step without a copy). On a\n70-megapixel photo, dragging a layer costs about 8 ms a frame and Free\nTransform 25 ms.\n\n**The fonts.** Every app's text is **FreeSans** unless it chooses\notherwise: GNU FreeFont's sans (in the manner of Helvetica and Arial),\nbuilt into the app in its regular, bold, oblique and bold oblique.\n`setUiFont(ufSerif)` gives **FreeSerif** (in the manner of Times),\n`setUiFont(ufMono)` **FreeMono** (in the manner of Courier); monospaced\ntext (code blocks, editors, hex) is FreeMono whatever the UI's font, and\nserif families in HTML, on the canvas and in documents are FreeSerif.\nItalic and bold italic are the fonts' own italics, not slanted copies.\n**Arabic is everywhere:** FreeSans has none of its own, so its Arabic --\nin every style -- is FreeSerif's (FreeSerif Bold's in bold text), joined,\nlam-alef as one letter, right to left among Latin text, mirrored brackets;\nFreeMono draws its own in its regular and FreeSerif's in its other styles;\na font of the user's without Arabic gets FreeSerif's the same way. The\nsame holds in text boxes, the rich editor, `htmlView`, the canvas, PDF\nfiles and reports, and in documents (`retroui/textengine`: FreeSerif's\nOpenType tables set the Arabic, as Amiri did before). Characters no\nbuilt-in font has (CJK, emoji) are drawn by the platform.\n\n**Right to left** is the whole Unicode bidirectional algorithm (UAX #9):\nnumbers and brackets, the direction marks, and the explicit embeddings,\noverrides and isolates (U+202A..202E, U+2066..2069) -- every case of\nUnicode's conformance tests passes. The zero width non-joiner and joiner\nsteer the joining (Persian میخواهم) and never cut a word in two; text is\nkept in its letters (joined shapes pasted in, or read from an old PDF,\ncome back as letters: `plainLetters`). In text boxes and the rich editor\nthe caret goes through a line as it reads -- Left goes on in an Arabic\nline, a letter and its vowels are one step, Backspace takes a vowel off\nand Delete a letter with its vowels -- and stands where the next letter\ntyped goes when Latin and Arabic meet; a selection of both is drawn where\nits parts are. PDF files carry Arabic as it is written (`/ActualText`,\nthe letters themselves) and say their language (`lang`); an Arabic\nsheet written to Excel opens right to left (`XlsxSheet.direction`). The\nArabic interface (`setUiLanguage(\"ar\", @toolkitArabic)`) mirrors the\nframe, menus, lists, scroll bars and back and forward arrows; text\nboxes, documents and Markdown keep their own direction, and charts,\nmaps, the media controls (play, the time bar) and pictures their sides.\n\nThe fonts are subsets of GNU FreeFont 0412 made by `tools/genfreefont.py`\nfrom the fonts GNU publishes: Latin (with Latin-1, Extended-A and -B,\nIPA), Greek, Cyrillic, Hebrew, Arabic (FreeSerif, FreeMono), punctuation,\ncurrency, arrows, mathematical operators, box drawing, geometric shapes\nand the symbols the kit's own text uses -- the bold and italic styles the\nletters, their symbols coming from the regular -- with their OpenType\nkerning, marks, ligatures and Arabic forms, without the TrueType hinting.\nThe twelve files take 0.94 MB in every app (compressed, unpacked when a\nface is first used; the fonts before them took 0.96 MB, and apps with\n`retroui/textengine` carried Amiri's 0.21 MB more).\n\n**Crisp at every scale.** Glyphs are drawn by the kit's own TrueType\nengine (`retroui/ttf`), at the screen's own resolution, on the CPU and the\nsame masks on the GPU: the coverage of each pixel computed exactly, in\ngray, the baseline, x-height, cap height and ascender set on whole pixels\nat small sizes (below 26 pixels to the em), the strokes as the font draws\nthem (FreeSans' stems, a little over a pixel at 12, keep their weight),\nletters placed at quarter pixels without drift; FreeMono's thin strokes\n(half a pixel at 12) are darkened to a whole pixel there; small text is\ndrawn with a light gamma (1.2). Lines take the font's own line gap.\n\n**Sharp text** (`setSubpixelText(true)`, or \"Text edges: Sharp\" in\n`appearanceSettings`; `RETRO_TEXT=sharp`) draws the letters in the\nscreen's red, green and blue strips, three times as fine across, as\nWindows' ClearType does, the stems then fitted to the pixels: off by\ndefault on every platform, used on screens of one device pixel per pixel\n(it follows Windows' strip order, RGB or BGR).\n\n`loadUiFontFile(path)` gives a font of the user's (a .ttf, or the first\nface of a .ttc; its bold, italic and bold italic beside it when they are,\n\"X-Bold.ttf\", \"X-Italic.ttf\"...); an app built with\n`-d:retroAppFont=path/X-Regular.ttf` carries its own and starts with it\n(`useAppFont`). Choose before `app` for one app's default, or at any\ntime; `uiFont()` tells which is on; `appearanceSettings` puts the choice\nin an app's Settings -- FreeSans, FreeSerif, FreeMono, a font file -- and\n`storeAppearance` / `restoreAppearance` keep it (a choice stored before\nFreeFont is FreeSans now, a font file stays one). `builtinUiFonts` lists\nthe built-in fonts for a menu of an app's own, `uiFontKey` / `uiFontOfKey`\nname them in its settings (\"sans\", \"serif\", \"mono\"; the words\nof before -- \"w98\", \"kawkab\"... -- still read). The fonts' names of before\ncompile for one release as deprecated aliases: `ufW98`, `ufRetro`,\n`ufSmooth`, `ufOpenSans` are `ufSans`, `ufKawkab`, `ufFixedsys` `ufMono`.\n\n**Licences.** GNU FreeFont is under the GNU GPL v3 or later with the\nfont exception (\"if you create a document which uses this font, and embed\nthis font or unaltered portions of this font into the document, this font\ndoes not by itself cause the resulting document to be covered by the GNU\nGPL\"), `src/retroui/fonts/LICENSE-FreeFont.txt`; the subsets keep that\ntext, and the files GNU publishes (and their FontForge sources) are where\nthe licence says. The rest of the kit is MIT; the icon font is the Nerd\nFonts symbols (MIT and SIL OFL, `src/retroui/fonts/LICENSE-NerdFonts.txt`).\nAn app that hands out its binaries hands out the fonts in it: keep the\nlicence text with it (the About box's credit is `RetroFontCredit`).\n\n**Pixel icons** (`retroui/pixelicons`) are used the same way, as text:\n\n```\ntoolButton(pxCopy, \"Copy\", size = 1.3)          # a 3.1-style tool bar icon\niconLabel(pxFolder, \"Documents\")\nimage(x, y, glyphIcon(pxComputer, black, 32))   # the 32x32 art at 32\nlabel retroOr(pxSave, nfSave) & \" Save\"         # pixel icon, or the Nerd one\n```\n\nThere are 255 (`pixelIconNames` lists them): folders and places, drives,\nfile kinds, the tool bar's actions, the message box signs, the\nhourglass, charts, maps, mail, calendar, clock, media keys, an image\neditor's tools and panels, and more. Each has 16x16 and 32x32 art and is drawn at a whole number\nof screen pixels per art pixel; disabled ones look embossed. Set\n`retroIcons = false` to have `retroOr` (and the toolkit's own parts) use\nthe Nerd icons. The art is original, drawn in the manner of 3.1, made by\n`tools/genicons.py`.\n\nEvery control tells screen readers what it is, its name, its value and its state as it is drawn: Orca on Linux and BSD (AT-SPI, over D-Bus, which the kit speaks itself), NVDA, JAWS and Narrator on Windows (UI Automation), VoiceOver on macOS and iOS, TalkBack on Android. A label names the control after it, so a form needs nothing more:\n\n```\nrow([70, -1]):\n  label \"&Name:\"        # read as the text box's name\n  edit name\nif button(\"&Save\"): save()\n```\n\nThe screen reader moves the focus, presses buttons and menu items, checks\nboxes, picks list and grid items, tree nodes and calendar days, puts text\nin a box and moves sliders and spin boxes: each comes into the next frame\nas the click or the typing would. The grid is read as a table (its\nheaders, the rows in view, their cells), the tree with each item's depth\nand whether it is open, a chart by its title, kind and series and the\npoint the keys are on. The tree is made only while a screen reader\nlistens; `RETRO_ACCESS=1` makes it without one, to try it.\n\nWhat an app draws itself is not read until it says what it is:\n\n``` js\nlet r = canvas(dp(300), dp(200))\ndrawSales(r)\naccessNode(arImage, \"Sales by month\", id = 0, r = r)   # a picture, read by its name\naccessName(\"Delete\")                                   # an icon-only button's name\nif toolButton(nfTrash): deleteIt()\n```\n\n`accessBegin` / `accessEnd` group nodes (a list of your own, its items\n`accessNode(arListItem, ...)` with `accessItem`), `accessRange` gives a\nvalue a range, `accessible()` says whether a screen reader listens. A\ncustom control takes a screen reader's press through `interact`, as a\nclick.\n\n**Input methods.** What the system's input method is composing (pinyin,\nkana, hangul, a phone keyboard's word) is drawn underlined in the text box\nat the caret, and typed when it is settled: XIM on X11, IMM on Windows,\nmarked text on macOS, GTK 4's preedit, the kit's own input connection on\nAndroid. `ui.composing` holds it for a text box of your own.\n\n**Scaling.** The window takes the scale of the screen it is on -- on X11\nthe monitor's DPI (RandR), on Windows and macOS the system's -- and again\nwhen it moves to another monitor; at 125 %, 150 % or 175 % controls and\ntext are the size asked for, and lines stay on whole pixels.\n\n`grid` shows rows that come from a proc, so 100,000 rows cost only what\nthe visible ones cost. Click a header to sort, drag its edge to resize;\narrows, Home, End, PageUp/PageDown; F2, a double click or typing edits an\n`editable` cell, Enter commits, Esc cancels. Pass `marked` for multi-select\n(Ctrl/Shift click, Ctrl+A, a rectangle, check boxes on phones).\n\nColumns can hold more than text — the controls of a business screen:\n\n| Column | Cells show | Interaction | \n|---|---|---|\n| `column` ,`numberColumn` | text, numbers (right aligned) | edited in place when `editable` | \n| `checkColumn` | a check box (\"1\", \"yes\", \"x\"…) | a click or Space flips it: `ev.toggled` | \n| `colorColumn` | a block of colour (\"#RRGGBB\", \"green\", \"orange\"…) | status lights | \n| `progressColumn` | a bar (\"0.62\" or \"62%\") |  | \n| `choiceColumn` | text picked from a list | F4 or a click drops the list: `ev.edited` | \n| `buttonColumn` ,`linkColumn` | a push button, a link | `ev.clicked` | \n| `iconColumn` | a pixel icon or icon-font glyph |  | \n| `customColumn` | anything you draw in `paint(row, rect, selected)` |  | \n\n`style(row, col)` gives any cell its own text and background colours and\nbold text; `rowHeaders` adds the record indicator column (an arrow at the\ncurrent row, a star while it is edited); `footer(col)` adds a totals row.\n\n``` js\nvar cols = @[column(\"Order\", 160), numberColumn(\"Amount\", 90),\n             colorColumn(\"Status\"), checkColumn(\"Paid\"),\n             choiceColumn(\"Store\", [\"Kitchen\", \"Engineering\"], 110), buttonColumn(\"\", 60)]\nlet ev = grid(cols, orders.len, proc (r, c: int): string = orders[r][c], sel,\n              height = -1, rowHeaders = true,\n              style = proc (r, c: int): CellStyle =\n                if c == 1 and isLate(r): cellStyle(fg = color(200, 0, 0), bold = true)\n                else: CellStyle(),\n              footer = proc (c: int): string = (if c == 1: total() else: \"\"))\nif ev.edited: orders[ev.row][ev.col] = ev.text   # typed, picked or a check box flipped\nif ev.clicked: openOrder(ev.row)                 # its button\nform 0:\n  field \"&Customer:\": comboEdit draft.customer, customers   # suggests as you type\n  field \"&Phone:\":    maskedEdit(draft.phone, \"+### #### ####\")\n  field \"&Date:\":     datePicker draft.date                 # Alt+Down or F4 drops it\n  field \"&Amount:\":   numberEdit draft.amount, lo = 0\n  field \"&Period:\":   dateRange(fromDay, toDay)             # with This month, Last year...\n  field \"Status:\":    checkbox \"&Paid\", draft.paid\ncase recordNav(pos, customers.len, dirty)                     # |< < 3 of 20 > >|  New Delete Save\nof raNew: addCustomer()\nof raSave: saveCustomer()\nelse: discard\n```\n\n- `maskedEdit` types into a mask (`#` digit,`A` letter,`U` upper case,`X` letter or digit,`*` anything; Arabic-Indic digits count as digits);`maskFormat` ,`maskFull` ,`maskRaw` read the value.\n- `dualList(available, chosen)` moves items between two lists (buttons,\ndouble click, Enter, up/down order).\n- `propertyGrid(props, sel)` edits properties by category: text, numbers,\nyes/no, choices, colours and dates, with a help line.\n- `treeGrid(cols, rows, sel)` is a tree with columns (a chart of accounts).\n- `wizard(open, title, step, steps, width): body` is a dialog of steps\nwith Back / Next / Finish;`stepDone(false)` keeps Next disabled.\n\n`Date` is a plain `(year, month, day)` with `today()`, `addDays`,\n`addMonths`, `weekday`, `parseDate`, `$` in `dateStyle`, `presetRange`.\n`formatNumber` / `parseNumber` read and write \"1,234.50\".\n\n`examples/ledger.nim` puts it together: a tree, a splitter, a sortable,\neditable grid, an invoice form, CSV import and export, reports, settings.\n\n`chart` draws 21 kinds of business chart from the same few lines:\n\n| Kind |  | Kind |  | \n|---|---|---|---|\n| `chBar` ,`chBarH` | bars, up or sideways | `chScatter` ,`chBubble` | points, sized points | \n| `chStacked` ,`chStacked100` | stacked, as shares | `chWaterfall` | changes adding up to a total | \n| `chLine` ,`chArea` ,`chStackedArea` | lines and areas ( `smooth` ) | `chFunnel` | the stages of a pipeline | \n| `chCombo` | bars with a line on a second axis | `chGauge` (`gauge` ) | one value on a dial, a target, bands | \n| `chPie` ,`chDonut` | shares (the total in the hole) | `chCandle` | open, high, low, close | \n| `chHistogram` | how values spread | `chHeatmap` | a table as colours | \n| `chRadar` | series around the labels | `chTreemap` | shares as rectangles | \n| `chBullet` | values against targets |  |  | \n\n``` js\nlet months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\"]\nchart(\"Sales\", chBar, months, [series(\"2025\", [12.0, 15, 9, 20]),\n                               series(\"2026\", [14.0, 18, 11, 24])])\nchart(\"Revenue and margin\", chCombo, months,\n      [series(\"Revenue\", rev), lineSeries(\"Margin %\", margin)],\n      opts = ChartOpts(format: NumFormat(prefix: \"$\", short: true),\n                       format2: NumFormat(percent: true)))\ngauge(\"Target\", 0.82, lo = 0, hi = 1, target = 0.9, format = NumFormat(percent: true))\n\n# Straight from a CSV file (comma, semicolon or tab; quotes; a BOM):\nlet t = readCsv(\"sales.csv\")\nlet ev = chart(\"By region\", chDonut, t, \"Region\", [\"Amount\"], height = -1)\nif ev.clicked: showRegion(ev.index)        # a bar, point or slice (or Enter)\nexportPng(ui.lastRect, \"chart.png\")        # the chart as a picture\ndiscard writeCsv(\"sales-copy.csv\", t)      # and a table back to CSV (quoted where needed)\n```\n\nEvery chart has round axis numbers, a legend (click an entry to hide that\nseries), a tooltip (the mouse, a tap, or the arrow keys once it has the\nfocus) and numbers written as money, shares, or 1.2K / 3.4M (`NumFormat`).\n`ChartOpts` sets the titles, axis names, legend place, data labels, axis\nrange, bands and palette. Empty, missing (NaN), negative and huge values\nare drawn as well as they can be, never a crash.\n\n**A chart app in a minute.** `examples/charts.nim` is a ready Chart\nViewer: open a CSV, pick the kind, the label column and the value columns,\nand save the PNG. `retrokit new sales --kind:charts` starts your own.\n\nInvoices, quotes, statements and lists are laid out on pages and come out as PDF files, PNG pictures, an on-screen preview, or paper — all in Nim, with nothing to install, and linked only into apps that use them (PDF alone adds about 45 KB). The fonts are embedded, the text in the PDF can be selected and searched, and Arabic is joined, runs right to left and is copied out of the file as it reads.\n\n**From an HTML template and data** — the way most business apps do it.\nTemplates are Mustache style: `{{name}}` (escaped), `{{{raw}}}`,\n`{{#items}}…{{/items}}` repeats, `{{^items}}` for \"none\", `{{a.b}}`,\n`{{price|n2}}` formats a number:\n\n``` python\nimport retroui, std/json\n\nconst tpl = \"\"\"\n<header><span>Acme Trading</span><span>Invoice {{number}}</span></header>\n<footer><span>{{date}}</span><span>Page {page} of {pages}</span></footer>\n<h1>Invoice {{number}}</h1>\n<p>Bill to: <b>{{customer}}</b></p>\n<table>\n<thead><tr><th width=\"60%\">Item</th><th align=\"right\">Price</th></tr></thead>\n{{#items}}<tr><td>{{name}}</td><td>{{price|n2}}</td></tr>{{/items}}\n<tfoot><tr><td>Total</td><td>{{total|n2}}</td></tr></tfoot>\n</table>\"\"\"\n\nlet doc = reportFromHtml(renderTemplate(tpl, %*{\"number\": 42, \"date\": $today(),\n  \"customer\": \"Sohar Steel\", \"items\": [{\"name\": \"Paper\", \"price\": 45}], \"total\": 45}))\ndoc.savePdf(\"invoice.pdf\")          # or: reportView(doc) / printReport(doc) / savePng(doc, 0, \"p1.png\")\n```\n\nThe HTML it reads: `h1`–` h4`, `p`, `div`, `br`, `hr`, `pre`, lists, `b`,\n`i`, `u`, `code`, `small`, links, `span` / `font` with colour, weight,\nstyle and size, tables (`thead`, `tfoot` for totals, `th`, `td`,\n`colspan`, `width`, `align`, `bgcolor`; `border=\"1\"` for a grid,\n`class=\"plain\"` for layout), local pictures, `text-align`, `dir=\"rtl\"`,\n`page-break-before`, and `<header>` / `<footer>` for every page with\n`{page}`, `{pages}`, `{title}`, `{date}`.\n\n**From Nim code**, when the document is built by the program:\n\n``` js\nvar doc = newReport(\"Invoice 42\", author = \"Acme Trading LLC\")\ndoc.header(\"Acme Trading LLC\", \"\", \"Invoice 42\")\ndoc.footer(\"\", \"Page {page} of {pages}\", \"\")\ndoc.heading(\"Invoice 42\")\ndoc.paragraph([span(\"Bill to: \"), span(\"Sohar Steel\", bold = true)])\nvar t = newTable([\"Item\", \"Qty\", \"Price\"], widths = [-3.0, -1, -1],\n                 aligns = [alLeft, alRight, alRight])\nt.addRow([\"Paper A4\", \"10\", \"45.00\"])\nt.addTotals([\"Total\", \"\", \"45.00\"])\ndoc.table(t)\ndoc.savePdf(\"invoice.pdf\")\n```\n\nPages flow by themselves: a table's header row comes again on each page,\nrows are never cut, headings stay with what follows, and `keepTogether`\nkeeps a group on one page. `reportView(doc)` shows the pages on a gray\ndesk with page buttons and zoom (wheel, keys, fingers, the standard\nscroll bars). `retroui/pdf` draws PDF pages directly (`newPdf`, `addPage`,\n`text`, `line`, `rect`, `image`, `link`) for forms and labels.\n\n**Printing:** `printReport(doc)` (or `printPdf(path)`) returns\n`(ok, message)`:\n\n| Platform | How it prints | \n|---|---|\n| Linux, BSD, macOS | CUPS ( `lp` ): the default printer or the one named | \n| Windows | the app that prints PDF files (a PDF reader must be installed) | \n| Android | the system's print dialog (printers, Save as PDF) | \n| iOS | not yet: save the PDF and share it | \n\n`examples/invoice.nim` is a complete invoice app: edit the customer and\nlines at the left, the pages follow at the right; save the PDF or print.\n\nWhat a project, sales or service app shows its work with:\n\n```\nkpiRow:                                              # numbers of a dashboard\n  kpiTile(\"Revenue\", \"$1.24M\", delta = 0.12, trend = weekly)\n  kpiTile(\"Open tickets\", \"37\", delta = -0.08, lowerIsBetter = true)\n  statCard(nfUsers, \"Customers\", \"1,208\", \"+52 this month\")\nlet k = kanbanBoard(lanes, cardSel)      # drag cards between lanes (a finger: hold, then move)\nlet g = ganttView(tasks, taskSel, scale) # bars move and stretch; parts fold; links between tasks\nlet a = agendaView(meetings, day, mode)  # month, week or day; drag to move or resize\nlet o = orgChart(staff, person)          # who reports to whom; branches fold\n```\n\n- `kanbanBoard` : lanes (`KanbanLane` : title, cards, a work limit) of cards\n(`KanbanCard` : title, detail, colour tag, due date, who has it). Drag,\nor Ctrl + the arrows, to move one; Enter opens it.\n- `ganttView` :`GanttTask` (name, start, finish, progress, colour, level,\ndependencies, milestone) on a day, week or month scale with weekends and\ntoday marked; drag a bar to move it, its ends to change its dates.\n- `agendaView` :`Appointment` (title, day, start and end minutes, all day,\ncolour) by month, week or day.\n- `kpiTile` ,`statCard` ,`kpiRow` : a value with its change (green or red),\na target and a sparkline;`kpiRow` wraps them to the width.\n- `orgChart` :`OrgNode` (name, title, parent, colour) as boxes and lines.\n\nAll of them take mouse, keyboard and fingers, draw only what is in view (thousands of tasks or cards stay fast) and scroll with the standard scroll bars.\n\n- \n**Menus and bars:**`menubar` /`menu` /`menuItem` (shortcuts, check\nmarks,`submenu` ),`contextMenu` (right click, long press, the menu\nkey),`popupMenu` (drop it down under a button),`toolbar` (buttons that\ndo not fit go into a \"more\" menu),`statusbar` .\n- \n**Windows in the window (MDI)** , as in Program Manager:\n\n``` js\nvar accounts = MdiWindow(title: \"Accounts\", open: true)   # keep them in globals\nvar invoices = MdiWindow(title: \"Invoices\", open: true)\nmenu \"&Window\": mdiWindowMenu()           # Cascade, Tile, the window list\nmdiArea(-1):\n  mdiWindow(accounts): grid(...)\n  mdiWindow(invoices): listbox(...)\n```\n\n They have the 3.1 frame (a thick sizing border, the control-menu box, a navy title bar when in front and a white one behind), move by their title bars, size by their edges, minimize to icons, maximize (also with a double click on the title), and come to the front with a click; Ctrl+F6 goes to the next one and Ctrl+F4 closes; their content scrolls when it is taller. A pin in the title bar keeps one on top; `noPin = true` leaves it out (a\nwindow that is never kept on top, as Program Manager's groups).\n- \n**Find and replace:**`findBar(state)` (Enter / F3 next, Shift+F3\nprevious, Esc closes; Match case, Whole word, Replace, Replace all) with`findIn` and`replaceAllIn` for the text.\n- \n**Shortcuts the user chooses:**`hotkeyEdit(keys)` takes the next key\npressed (\"Ctrl+Shift+S\");`hotkeyPressed(keys)` tells when it is pressed.\n- \n**When the app stopped:**`app` shows what the last run's crash wrote\ndown before the app's own code runs (`stoppedScreen` : Copy, Send by\nEmail, Start); an app with its own frame loop calls`stoppedFrame` first\nin each frame and`stoppedPending` before it brings back what was open,\nand leaves that out when`startFresh` (the user chose so).\n- \n**About:**`aboutBox(open, \"Ledger\", \"2.0\", [\"(c) 2026 ...\"], tagline = ..., iconPng = staticRead(\"icon.png\"), whatsNew = [...], credits = [...])` : a\nbanner in the blue of a 1994 Setup screen with the icon, name, version\nand a rainbow; pages About (with the system, the screen, the toolkit's\nand Nim's versions), What's New and Credits; Copy Info.\n- \n**Dialogs:** modal`dialog` s with a 3.1 frame that drag by their titles\nand scroll when taller than the screen;`msgBox` ,`inputBox` ,`progressBox` , the 3.1`fileDialog` and the modern`filePicker` .\n\n```\nfor f in listFolder(dir): echo f.name, \" \", sizeText(f.size), \" \", kindName(f)\nlet job = newJob(jkCopy, @[\"a.txt\", \"photos\"], \"/backup\")   # also jkMove, jkDelete,\nwhile job.state == jsRunning: job.run(12)                   # jkZip, jkUnzip, jkMeasure\n# job.progressOf, job.conflict + resolve(cfReplace / cfSkip / cfKeepBoth), cancel()\n\nzipFolder(\"photos\", \"photos.zip\"); extractZip(\"photos.zip\", \"out\")\nlet pic = loadPicture(\"IMG_0001.jpg\", 1600, 1600)   # PNG, JPEG (baseline, progressive,\ndrawBitmap(rect(10, 10, 160, 120), pic)              # EXIF turned), BMP; more on phones\n```\n\nFile jobs work in slices so the window stays alive, never follow links out of a folder they delete, refuse to copy a folder into itself, and leave no half-written file when cancelled.\n\nSlow work goes to worker threads, and big files are read a window at a time:\n\n``` js\nlet t = folderAsync(dir)                  # or pictureAsync(path, 1600, 1600)\nfor r in finished():                      # every frame: what is done\n  if r.ticket == t: items = r.items       # (pictures: r.toBitmap)\ndiscard pictureAsync(path, 800, 800, onDone = proc (r: var TaskResult) =\n  photo = r.toBitmap)                     # or a callback, on the UI thread\n\nvar log = openTextFile(\"huge.log\")        # opens at once, any size\ntextFileView(log, sel)                    # lines counted a few ms a frame\nhexFileView(openHexFile(\"app.bin\"), sel)  # any file as hex, read as shown\n```\n\n`retroui/filewidgets` has what a file manager, an Open dialog or a photo\napp is made of:\n\n``` js\nvar folder = newFolderModel(getHomeDir())        # listed on a worker thread\napp \"Files\", 900, 600:\n  sidePane(true, previewW, previewH):            # beside, or under on phones\n    let ev = fileList(folder, fvIcons)           # or fvDetails; thumbnails\n    if ev.activated and ev.item.isDir: folder.open(ev.item.path)\n  do:\n    let p = filePreview(folder)                  # picture or text, sizes...\n    if p.open: openFile(p.item.path)\n```\n\n- `FolderModel` :`open` ,`goBack` ,`goForward` ,`goUp` (the folder you\nwere in stays selected),`reload` ,`setFilter` ,`search` (through the\nsubfolders, a few milliseconds a frame),`patterns` (`[\"*.png\"]` ),\nnatural sorting, a selection (`marked` ,`selectedItems` ,`selectAll` ),`makeFolder` ,`makeFile` ,`rename` , and it lists the folder again when\nanother program changes it. The old listing stays until the new one is\nthere, so a slow folder never blanks the window.\n- `fileList` : details (sortable, resizable columns, rename in place) or\nicons with picture thumbnails; empty, searching and \"reading...\" states.\n- `filePreview` : the details pane -- the picture (decoded in the\nbackground) or the start of a text, size, what a folder holds\n(measured as you look), dates, picture size, Open / Share / Properties.\n- `folderTree` : places (home, documents, storage, drives) and favorites.\n- `cachedPicture` ,`drawPicture` ,`pictureBox` : pictures decoded on\nworker threads and kept up to a memory budget (`PictureCache` ).\n- `runJobDialog(job, \"Copying\")` : runs a file job with its progress box\nand asks \"Replace / Keep both / Skip\";`propertiesDialog` ;`filePicker` .\n- `fileGlyph` ,`fileIcon` : an icon and a color for each kind of file.\n- `fileList` views:`fvDetails` ,`fvTiles` (icon, name, kind, size) and`fvIcons` at any`iconSize` ; Ctrl + the wheel reports`ev.zoom` so the\napp can step through sizes. Hovering a row highlights it, and after a\nshort rest`filePreview(m, followHover = true)` shows that item.\nDragging from the empty part draws a selection rectangle.\n- Drag and drop: rows and tiles drag as `\"files\"` ; folders in the list,\nin`folderTree` , on`tabStrip` tabs and on the folders of`pathBar` take\nthem, and each reports a`FileDrop` (`paths` ,`into` ,`copy` when Ctrl\nis held).`jobFor(drop)` makes the move, copy or trash job and`runJobDialog(job, jobTitle(job.kind))` runs it. On touch screens a\nfinger held on a file until its menu opens, then moved, drags it.\n- `pathBar(path)` : the folders as buttons, as in Windows Explorer: a\nclick on one goes there, the arrow after each drops a menu of the\nfolders in it, \"<<\" lists the folders that do not fit; a click on the\nempty part, or`editNow` (F4, Alt+D, Ctrl+L), turns it into a box with\nthe subfolders suggested.\n- `placesView` : \"This device\" -- places and favorites as tiles with how\nfull their disks are.\n- The trash: `moveToTrash` ,`trashItems` ,`restore` ,`deleteForever` ,`emptyTrash` (the freedesktop.org trash on Linux and Android, the\nRecycle Bin on Windows, ~/.Trash on macOS) and`trashList` , a widget\nwith Restore, Delete forever and Empty the trash.`jkTrash` jobs.\n- Undo: `remember(job)` after a job (or`remember(UndoStep(...))` after\na rename);`canUndo` ,`undoLabel` ,`undo()` moves things back.\n- `driveBar(current, favorites)` : the drive buttons of the 3.1 File\nManager (places and favorites), taking drops.\n- A folder not shown yet comes in two steps: the names at once, then the\nsizes and dates (`listFolder(dir, quick = true)` gives`stub` items),\nso thousands of photos on a phone's shared storage show without\nwaiting.\n\n**Other apps' files** (`retroui/docfs`). On Android, an app's files are\nits own; one that shares them offers a documents provider. Termux does,\nfor its home, and Retro's file parts treat it as a folder:\n`/data/data/com.termux/files/home` is listed, opened, copied to and\nfrom, moved, zipped, renamed and deleted like any other (the `v...`\nprocs -- `vIsDir`, `vOpen`, `vWalk`, `vMove`... -- go through the provider\nthere and to the disk elsewhere; the file jobs use them). Until the user\nallows it, a `FolderModel` showing it has `needsAccess`, and `fileList`\noffers \"Allow access\", which opens the system's folder picker at\nTermux's home (`askAccess`), asked once. `localCopy` gives viewers a\nfile to read. Inside Termux itself its files are simply files, and\n\"Termux home\", \"Termux system\" and \"Shared storage\" are among the places.\nThe parts any app can use for this:\n\n```\n# drag and drop of anything\nif dragStarted: startDrag(\"cards\", [cardId], label = \"1 card\")\ncase dropTarget(lane, \"cards\")          # dsOver: draw it highlighted\nof dsDropped: moveCard(ui.drag.items, laneId)\nelse: discard\n\n# files dropped from Explorer, Nautilus... (Windows, X11): attach them\nfor path in fileDropTarget(attachmentsBox): attach(path)\n# ... and dragged out to them (Windows, X11, macOS): made only once dragged\nfileDragSource(iconRect, proc (): seq[string] = @[saveToTemp(att)])\n\nlet t = tabStrip(names, cur, closable = true, addButton = true)  # browser tabs\nif t.closed >= 0: names.delete(t.closed)\ntoast(\"Copied 12 items\", title = \"Files\")    # a yellow Windows 95 balloon\n```\n\n`examples/filemanager.nim` (about 2,100 lines: the rest is the toolkit's)\nis **Retro Files 3.3.6**, a complete file manager in the manner of the Windows 98\nExplorer -- the Standard Buttons with their labels under the icons (View >\nToolbars > Text Labels), the Address bar, View as Web Page (the folder's\nname large and the selection described at the left), Large and Small icons,\nDetails, Tiles, Columns (each folder of the way a column, as Finder has it,\nwith type-ahead and the picked file's preview), Arrange Icons, Explorer Bar >\nFolders, Tools > Find (F3),\nFolder Options, the zone in the status bar -- and what one does today:\ntabs (Ctrl+T, Ctrl+W, drag files onto a tab) -- or each folder in a\nwindow of its own, as the 3.1 File Manager had them (View > Folders in\nWindows: Cascade, Tile, Always on Top) -- two panes side by side\n(F9, F6 to switch, copy or move to the other pane), drag and drop\nbetween lists, the tree, tabs and the path bar (Ctrl copies), a\npath bar whose folders are clicked or dropped down as in Explorer and\nthat takes a typed path with suggestions (F4, Alt+D, Ctrl+L), \"This device\"\nwith how full each disk is, the trash with restore (Delete moves there,\nShift+Delete deletes for good), undo of renames, moves, copies, new items\nand trashing (Ctrl+Z), details, tiles and five icon sizes (Ctrl + the\nwheel), a selection rectangle, a preview that follows the mouse, file\nname extensions shown or hidden, copy speed and time left,\nfolder tree with places and favorites, the 3.1 drive bar, picture\nthumbnails, pixel icons, a tab menu (duplicate, close the others, move to\nthe other pane), the middle button opening a folder in a new tab, the\nmouse's back and forward buttons, a selection bar on phones, Termux's\nhome folder (after \"Allow access\"), a preview\npane (the picture or the start of a text,\nsize, contents of folders, picture size, Open / Share / Properties; under\nthe listing on phones), natural sorting (\"photo 2\" before \"photo 10\"),\nmulti-select (check boxes on phones), cut / copy / paste in the background\nwith progress and \"Replace / Keep both / Skip\", rename in place (F2),\ndelete, new folders and files, zip and extract, search in the folder or\nall its subfolders, properties with folder sizes and permissions, viewers\nfor text of any size (with editing and saving for small ones), Markdown\n(preview and source), pictures (next / previous, fit / 1:1, a slide show),\nPDF documents (pages drawn as you scroll, zoom, a page box), videos and\nsounds (with the folder's sounds as a play list), zip archives and hex,\npictures of PDFs and videos in the lists, renaming many by a pattern,\nSelect Matching, Duplicate, Open Terminal Here, open with and share, a Settings dialog in tabs (look, sizes,\nthe font -- FreeSans, FreeSerif, FreeMono, the system's or a font file --, smooth scrolling, thumbnails,\npreview, single tap, confirm delete, start folder, Termux access), an\nAbout box that fits any screen, mouse hover, and settings that stick.\n\nNew in 3.1: very big pictures (gigapixel scans, huge maps) are shown and\nthumbnailed a band at a time (`retroui/bigpicture`), and in Arabic a line\nthat mixes Arabic with numbers reads right to left. New in 3.0: thumbnails of almost every picture (camera raw photos by\ntheir own previews, PSD and Retro Photo documents, SVG, JPEG XL,\nWebP, TIFF, EXR, HEIC/AVIF) **kept on disk**, so a folder of photos opens\nat once the second time; **smart memory** -- the pictures kept are a share\nof the device's memory and are let go when it runs short or the app goes\nto the background (`retroui/memwatch`, `retroui/thumbcache`); an **Arabic\ninterface** mirrored right to left; Tools > **Folder Sizes**; Copy To /\nMove To Folder, Send To, Share; Help > **Report a Problem** (a fault no\nlonger closes the app).\n\nFast with big folders and big files: lists glide when scrolled (a flick goes on, a tap stops it), folders are listed and pictures decoded on worker threads (the window never waits), a 1 GB log opens at once and its lines are counted while you read, zip files are read from their directory only. On Android it asks for \"All files access\" and starts in the internal storage; Back goes back through the folders, File\n\nExit leaves.\n\n```\nnimble files                                                # desktop\nretroapk examples/filemanager.nim --name:\"Retro Files\" --id:org.retro.files \\\n  --storage --icon:examples/assets/filemanager-icon.png --install\n```\n\nTwo maps. **The world map** for dashboards needs nothing from the network:\nthe countries of Natural Earth (public domain) are built in, about 70 KB,\nand linked only into apps that use it.\n\n``` js\nvar world = MapState()\nlet ev = mapView(world,\n  values = [(\"OM\", 120.0), (\"AE\", 340.0), (\"Saudi Arabia\", 210.0)],  # codes or names\n  markers = [cityMarker(\"Muscat\"), cityMarker(\"Dubai\")],\n  routes = [MapRoute(fromLat: 23.6, fromLon: 58.4, toLat: 51.5, toLon: -0.1)])\nif ev.clicked and ev.country >= 0: showCountry(ev.name)\nfitTo(world, [\"OM\", \"AE\", \"SA\"])          # or zoomTo, resetView\n```\n\nCountries are coloured by value with a legend, markers are dots or bubbles with labels, routes are great-circle arcs. Drag to move, the wheel, a double click, + / − or the corner buttons to zoom; the country under the mouse is lit and named, and a click or a tap reports it. The map is drawn anti-aliased into a picture of its own only when the view or the colours change.\n\n**The street map** (`retroui/tilemap`) shows the whole world from any map\nservice, or from maps of your own, with places and routes from\n`retroui/geo`:\n\n``` python\nimport retroui, retroui/[tilemap, geo]\nvar city = TileMapState(lat: 23.588, lon: 58.3829, zoom: 12)\nlet src = mapTilerVector(key)          # osmTiles(), googleTiles(key), azureTiles(key), pmtilesSource(\"oman.pmtiles\")...\nvar trip: Route\napp \"Deliveries\", 1000, 700:\n  if button(\"&Route\"):\n    let r = route([(23.588, 58.383), (23.614, 58.593)], service(gpOsrm))\n    if r.ok:\n      trip = r.route\n      city.fitPoints(trip.points)\n  let ev = tileMap(city, src, markers = [MapMarker(lat: 23.614, lon: 58.593, label: \"Airport\")],\n                   shapes = [routeShape(trip)])\n  if ev.clicked: echo ev.lat, \" \", ev.lon\n```\n\n- **Every service's tiles** : OpenStreetMap, OpenTopoMap, CARTO, Google\n(its map tiles API, with a session made for you), Azure Maps, Mapbox,\nHERE, MapTiler, Protomaps, Esri, TomTom, Stadia, China's AMap, Tencent\nand Tianditu (drawn in GCJ-02, with your GPS points moved to match), any\nTileJSON. Raster tiles at twice the pixels on dense screens; vector tiles\n(Mapbox Vector Tiles) drawn by the kit in a style of yours\n(`classicStyle` ,`darkStyle` ), their names upright and never on top of\neach other.\n- **Maps of your own, offline** : a PMTiles file (or a PMTiles address: only\nthe parts shown are read), an MBTiles file, a folder of tiles.`retroui/pmtiles` writes PMTiles too, and`retroui/mvt` vector tiles.\n- **Places and routes** from 13 services (Nominatim, Photon, OSRM,\nValhalla, GraphHopper, OpenRouteService, Google, Mapbox, HERE, Azure\nMaps, TomTom, MapTiler, Esri):`geocode` ,`reverseGeocode` ,`route` (with its turns, in words), or`geocodeAsync` and friends on a worker\nthread; distances, bearings, polylines (Google's and HERE's), GeoJSON in\nand out.\n- Tiles come on six worker threads, are kept in memory and on the disk\n(a week), and the tile above is shown larger while one comes. The map\nshows each service's attribution, as each asks. OpenStreetMap's own\nservers are for light use: `keepArea` (tiles for offline use) refuses\nthem; a business app takes a service with a key, or its own tiles.\n- Apple's maps and Google's JavaScript map are not tiles another program\nmay draw: `mapKitPage` and`googleMapsPage` make a page of theirs for`webView` .\n\nA 3D view drawn by the kit itself -- on the CPU with nothing to install, or on the GPU where there is one -- for product viewers, car configurators, 3D bar charts and scenes that move.\n\n``` js\nvar scene = Scene3()\ndiscard scene.add box(1, 1, 1, color(0, 0, 160))\ndiscard scene.add loadModel(\"part.stl\")             # or .obj\nscene.addGrid(); scene.addAxes()\nvar cam = camera3()\nfitView(scene, cam)\nlet ev = view3d(scene, cam, height = -1)            # drag turns, Shift+drag moves, wheel zooms\nif ev.clicked and ev.picked >= 0: echo scene.meshes[ev.picked].name\nvar bars = bars3d([@[3.0, 5, 4], @[4.0, 6, 7]], [\"2025\", \"2026\"], [\"Q1\", \"Q2\", \"Q3\"])  # a 3D bar chart\ndiscard render3d(scene, cam, 800, 600).savePng(\"scene.png\")\n```\n\n**Models from Blender** (and every 3D tool) come as glTF 2.0\n(`retroui/gltf`): `.glb`, or `.gltf` with its files; a `.blend` is turned\ninto one by Blender when it is installed (`loadModel3`, or once with\n`retrokit model car.blend`). Their parts are nodes the app moves, turns,\nhides and tints by name, their clips play, their bones and shapes follow:\n\n``` python\nimport retroui, retroui/[view3d, gltf]\nvar car = loadGltf(\"car.glb\")\nvar cam = camera3()\nfitView(car, cam)\napp \"Showroom\", 1000, 700:\n  if button(\"&Open the door\"): car.play(\"DoorOpen\", loop = false)\n  if button(\"&Red\"): car.recolor(\"CarPaint\", color(170, 0, 20))\n  if button(\"&Steer\"): car.turnNode(\"Wheel_FL\", vec3(0, 1, 0), 15)  # setVisible, tint, moveNode\n  car.animate(1 / 60)\n  let ev = view3d(car, cam)\n  if ev.clicked and ev.nodeName == \"Hood\": car.play(\"HoodOpen\", loop = false)\n```\n\nMeshes: `box`, `sphere`, `cylinder`, `cone`, `plane`, or `loadObj` /\n`loadStl` (text and binary) / `loadModel` / `loadGltf`. Materials are\nglTF's metallic-roughness (paint, chrome, glass that lets light through,\nlights that glow, pictures on surfaces, KHR_materials_unlit and\nemissive_strength, KHR_texture_transform, WebP pictures); clips are\nlinear, stepped or cubic, of places, turns, sizes and shapes' weights;\nbones skin the points. On the CPU triangles are filled with a z-buffer at\nthe screen's own resolution, lit on all the cores; with `RETRO_GPU=gpu`\n(or where the kit draws with the GPU) the view is drawn by OpenGL into a\npicture of its own, the same scene, the same light. Either way it is drawn\nagain only when the scene, the camera or the size changed.\n\n`retroui/canvas2d` is the HTML5 Canvas 2D API in Nim — the same names, so\nany canvas tutorial or JavaScript drawing code carries over — drawn by\nthe kit's own anti-aliased rasterizer at the screen's resolution (crisp on\n4K screens and phones). No browser, no GPU, no library.\n\n``` python\nimport retroui, std/math\nvar angle = 0.0\napp \"Canvas\", 420, 320:\n  canvas2d(ctx, 400, 280):\n    let g = ctx.createLinearGradient(0, 0, 400, 0)\n    g.addColorStop(0, \"#000080\")\n    g.addColorStop(1, \"#00a0ff\")\n    ctx.fillStyle = g\n    ctx.fillRect(0, 0, 400, 280)\n    ctx.translate(200, 140)\n    ctx.rotate(angle)\n    ctx.fillStyle = \"gold\"\n    ctx.beginPath()\n    ctx.arc(0, 0, 60, 0, 2 * PI)\n    ctx.fill()\n    if ctx.input.down: angle += ctx.input.dx / 100    # drag to turn it\n```\n\n- **Paths:**`beginPath` ,`moveTo` ,`lineTo` ,`quadraticCurveTo` ,`bezierCurveTo` ,`arc` ,`arcTo` ,`ellipse` ,`rect` ,`roundRect` ,`closePath` ,`Path2D` ;`fill` (non-zero or even-odd),`stroke` ,`clip` .\n- **Style:** colours as CSS strings (`\"#c00\"` ,`\"rgba(0,0,0,.5)\"` ,`\"gold\"` ) or`Color` ; linear, radial and conic gradients; patterns; line\nwidth, caps, joins, miter limit, dashes;`globalAlpha` ; 16`globalCompositeOperation` modes; shadows with blur.\n- **Transforms:**`translate` ,`rotate` ,`scale` ,`transform` ,`setTransform` ,`save` /`restore` .\n- **Text:**`fillText` ,`strokeText` ,`measureText` ,`font` ,`textAlign` ,`textBaseline` (the built-in Sans, Bold and Mono; Arabic\nis shaped).\n- **Pictures:**`drawImage` (any`Bitmap` : loaded PNG / JPEG, another\ncanvas),`getImageData` ,`putImageData` ,`createImageData` .\n- **Input:**`ctx.input` has the pointer, buttons, fingers, the wheel and\nkeys in the canvas's coordinates;`isPointInPath` /`isPointInStroke` tell which shape is under them — for diagrams, games, signature pads.\n- **Speed:** immediate mode like the rest of the kit — the canvas is\ndrawn again only when what you drew changed;`canvas2dWith(..., CanvasOptions(retained: true))` keeps the picture between frames (a\npaint program).`newCanvas2D(w, h)` draws without a window, and`savePng` /`toBitmap` take the result (charts for a report, thumbnails\non a server).\n\n`canvas(w, h)` is the simpler, faster alternative for pixel-exact 3.1\ndrawing (`fill`, `box`, `line`, `text`, `disc`, `polygon`…).\n\n`examples/mail.nim` is **Retro Mail 1.1.2**: Outlook Express 4 to look at and to\nuse, with what the new Outlook does, for Microsoft 365, Outlook.com and Gmail\n-- and any other provider through IMAP and SMTP. It is the toolkit's network\nmodules at work:\n\n- `retroui/oauth` signs in in the system browser (OAuth 2.0, PKCE, a loopback\naddress) and`retroui/secrets` keeps the tokens (DPAPI, the keychain,\nlibsecret): no password is ever typed into the app.\n- `retroui/http` talks to Microsoft Graph and the Gmail API from the sync\nthread (WinHTTP, libcurl, Java's HTTP on Android).\n- `retroui/imap` and`retroui/smtp` read and send any other provider's mail,\nover`retroui/netconn` 's connections (the system's own TLS: SChannel,\nSecure Transport, Java's, OpenSSL loaded when needed);`retroui/mailsetup` finds an address's servers (autoconfig, Thunderbird's\nlist, DNS).\n- `retroui/db` keeps every message in SQLite with a full-text index: search\nis offline and instant;`retroui/mime` reads and writes the messages.\n- `htmlView` draws the mail -- newsletters' nested tables and styles,\nhidden preheaders, quotes, Arabic -- with no browser and no script;\npictures from the web wait until asked.\n- `retroui/richedit` writes it: bold, colors, lists, quotes, links,\nsignatures.\n\nThreads with + and - boxes, Focused and Other, tags in their colors,\nflags, pin, snooze, quick replies, send later, the calendar, people, to do\nand notes; Undo and Undo Send, many messages at once, Quick Steps, Sweep,\nFollow Up, Message Rules, meeting invitations, Automatic Replies, Local\nFolders (Outlook Express's mail imported), spelling and AutoCorrect in\nEnglish and Arabic, Summarize and Translate with your own key, Print with a\npreview, Properties and Message Source, Find Message, Search Folders,\nUnsubscribe, Block Sender, Ignore Conversation, My Templates, the new-mail\nsound; on a phone,\nswipe a message right to archive it, left to delete it, hold one to choose\nseveral, pull to refresh. Adding an account is your address and Enter: the browser opens the\nright sign-in page, done (`docs/retromail.md`: the app registrations a build\ncarries, made once); for any other provider, the password, and the servers\nare found.\n\n`retroui/xmpp` is chat on XMPP (Jabber): an account on any public server,\ntalking to anyone on any XMPP app (Conversations, Gajim, Monal, Dino...).\nSigning on (STARTTLS, SASL), the contact list and its groups, presence and\nstatus notes, typing notifications, delivery receipts, the messages you\nsend from another device, asking to add someone, profiles with a picture,\nchat rooms, files (HTTP upload), making an account from inside the app\nwhere the server allows it, and the history the server keeps (the message\narchive, XEP-0313: `askArchive` -- what you said from your other devices,\nor while this one was off, a page at a time). The connection runs on a\nthread of its own:\n\n``` python\nimport retroui, retroui/xmpp\nxmppStart(\"me@yax.im\", \"my password\")\napp \"Chat\", 400, 300:\n  for e in xmppEvents():\n    if e.kind == xeMessage: echo e.jid, \": \", e.text\n  if button(\"Hello\"): discard sendChat(\"friend@xmpp.jp\", \"Hello!\")\n  if xmppRunning(): wakeIn(250)\n```\n\n`retroui/omemo` locks the messages end to end, as Conversations, Monal,\nGajim, Dino and Siskin do (OMEMO 0.3, the Signal protocol: X3DH, then the\ndouble ratchet -- a new key for every message): the server carries words\nit cannot read. Each device has its own keys, kept in one file; trust is\nblind until you verify a device's fingerprint. Files go locked too\n(`encryptFile`, aesgcm://). The cryptography is OpenSSL's libcrypto\n(X25519, Ed25519, AES), loaded when needed:\n\n``` python\nimport retroui/omemo\nlet keys = omemoOpen(dataDir(\"chat\"), \"me@yax.im\")\nfor e in xmppEvents():\n  if keys.omemoHandle(e): continue                 # its own answers\n  if e.kind == xeOnline: keys.omemoStart()          # this device's keys published\n  if e.kind == xeMessage and isEncrypted(e):\n    let (ok, words, why) = keys.omemoDecrypt(e)\ndiscard keys.omemoSend(\"friend@xmpp.jp\", \"Hello!\")  # when their keys are here\nfor n in keys.omemoNotes(): echo n                  # sent, could not send, devices\n```\n\nBuild with `-d:ssl` (OpenSSL, loaded at run time). `examples/communicator.nim`\nis **Retro Communicator** on it (`nimble communicator`): the friends list\nwith groups, a window for each talk, rooms, away messages, your profile\nwith a picture and a QR code to add you, files sent, the sounds of 1999 --\nand the history synced from the server when you sign on (Earlier messages\nin a talk goes further back), and **voice messages**: the microphone\nbutton records (Termux:API on a phone, PulseAudio or ALSA on Linux; AAC in\nan .m4a with ffmpeg, as Conversations and Monal play it) and sends it, and\na voice message that comes plays in the talk -- and **encryption**: the\nlock button in a talk (on by itself when the other one's app has OMEMO),\nthe fingerprints in Info and My Profile, files and voice messages locked\ntoo.\n\nIn a web page (`retrokit web --threads`) the connection is a WebSocket\n(RFC 7395) to the address the server's host-meta names (Prosody's\n`mod_websocket`, ejabberd's `ejabberd_http_ws`).\n\n`retroui/board` is a whiteboard's document, in the manner of tldraw: pages\nof shapes on an endless canvas, with undo and redo, hit-testing, snapping\nand the outlines each shape is drawn with -- at any zoom, on the CPU or the\nGPU (`fillContours`). `retroui/freehand` is its ink: the outline of a stroke\nfrom a pen, a finger or the mouse, thinner where it was drawn fast or\npressed lightly (real pressure from a pen), tapered if asked, as one\npolygon.\n\n``` python\nimport retroui/board\nlet b = newBoard()\nb.change(\"Flow\"):                       # one step to undo\n  let a = b.addShape(b.geoShape(gkRectangle, 0, 0, 200, 120))\n  var n = b.noteShape(400, 60, \"Ship it\")\n  let c = b.addShape(n)\n  var ar = b.arrowShape(0, 0, 0, 0)     # held by both: it follows them\n  ar.t0 = Terminal(bound: a, nx: 0.5, ny: 0.5)\n  ar.t1 = Terminal(bound: c, nx: 0.5, ny: 0.5)\n  discard b.addShape(ar)\nwriteFile(\"flow.png\", encodePng(b.renderBitmap([], scale = 2)))\nwriteFile(\"flow.svg\", b.toSvg([]))\nb.saveBoard(\"flow.board\")\n```\n\n- **Shapes:** freehand ink and the highlighter; 31 geometric shapes\n(rectangle, rounded, ellipse, diamond, star, cloud, heart, check box,\ncylinder, document, speech bubble, cube, person, lightning, moon,\narrows…) with labels; arrows that hold on to shapes (to their edge, or a point\nof them), bent, with seven kinds of head and a label; lines through\npoints, straight or smooth; text (wrapped or as wide as its words);\nsticky notes; frames; pictures; groups.\n- **Styles:** twelve colors (light and dark sets), fill none / semi /\nsolid / pattern, hand-drawn / dashed / dotted / solid / sketch lines\n(sketch: each edge drawn twice, loosely, the pattern a quick hatching),\nfour sizes, alignment, opacity; ten fonts -- six handwriting fonts the\nmodule carries (Caveat, Patrick Hand, Architects Daughter, Gochi Hand,\nIndie Flower, and Aref Ruqaa for Arabic; SIL OFL, about 480 KB) and\nFreeSans, FreeSerif, FreeMono and FreeSerif Italic. A letter the font\nlacks is set in another (`fontSpans` ): Arabic after a handwriting font\nin Aref Ruqaa, else in FreeSerif.\n- **As Excalidraw's options:** a fill color of its own (`fillColor` ),\nfills hachure / cross-hatch / zigzag / dots (`fillLines` ), sloppiness\n(`roughness` : architect, artist, cartoonist), sharp or round edges\n(`round` ,`roundCorners` ), straight, curved and elbow arrows (`elbow` ,`elbowPath` ), the canvas's color (`paper` ,`paperColors` ), links\n(`link` ), the next step of a flowchart (`addConnected` ),`findText` .\n- **Editing:** move, size and turn (about any point, rotated shapes in\ntheir own frame), flip, align, distribute, order, group, lock, copy and\npaste (`toClipboard` ,`pasteShapes` ), duplicate;`snapBox` lines edges\nand middles up;`textCaret` /`textIndexAt` for typing in a shape.\n- **Memory:** a picture is kept once (`addAsset` ), however often it is\nmoved or undone; the steps to undo keep at most`undoBudget` bytes, the\ndrawings`drawingBudget` (`forgetDrawings` when memory is short).\n- **Files:**`.board` (JSON, pictures inside), PNG (`renderBitmap` : any\nscale, transparent or on the canvas's color), SVG and PDF (vectors,\ntext as outlines).\n\n`examples/retroboard.nim` is **Retro Board 1.3.6**, a whiteboard app made of\nit: the 3.1 title bar on phones and tablets, a status bar (the tool's\nhint, page, shapes, zoom, memory), the color schemes and Appearance, a\nfloating tool bar (select, hand, draw, highlighter, eraser, laser,\nshapes, arrow, line, text, note, frame, picture), pages, zoom; Excalidraw's\noptions in Retro Photo's docked panels (`retroui/dock`), tabs grouped by\nwhat they are about -- **Color, Stroke, Fill** (stroke and background\ncolors, opacity; stroke style, width, sloppiness, edges; hachure,\ncross-hatch, zigzag, dots), **Text, Arrow, Shapes** (fonts, size, align;\narrow type and arrowheads; all 31 shapes) and **Arrange, Board, Info**\n(layers, align, distribute, flip, actions; pages, the canvas's color,\ngrid and snap; the memory) -- dragged, floated, folded to icons (on a\nphone, a strip of them), hidden with Tab, kept between runs; lines that\nhold on to the shapes they are drawn from and to; links opened from\na badge, Ctrl+Arrow for a flowchart's next step, Find, Position and Size,\nStats (with the memory used, and Free Memory), Zen and View modes; pinch to\nzoom and two fingers to pan on a phone (the toolkit's\n`ui.pinch`, `ui.panDX` / `panDY`); a long press for the menu; the\nkeyboard shortcuts of tldraw on a desktop; saved as you go. About 2 MB as\nan APK (`nimble boardapk`).\n\n`retroui/sysmon` reads the processor (all of it and each core), the\nmemory, the disks, the network, the battery and every process with its\nshare of them -- on a thread of its own, so the window never waits for the\nsystem. What Argus taught about doing that lightly is in it: a read that\nfails leaves the last good numbers (`stale`), a process is known by its id\n*and* its start (an id is used again: `endProcess` leaves another program\nalone), rates come from the real time between two reads, what does not\nchange (a program's file, its command line, its user) is read once per\nprocess, and the system is read less often while nobody looks\n(`setMonitorInterval`).\n\n``` python\nimport retroui, retroui/sysmon\nstartMonitor()                        # read every second, on its thread\napp \"Monitor\", 500, 400:\n  let s = latestSample()              # never waits; nil before the first\n  if s != nil:\n    label \"CPU \" & $int(s.cpu) & \" %, memory \" & bytesText(s.memUsed)\n    for p in s.processes[0 ..< min(10, s.processes.len)]:\n      label p.name & \"  \" & $p.pid & \"  \" & formatFloat(p.cpu, ffDecimal, 1) & \" %\"\n```\n\n| Where | What it reads | \n|---|---|\n| Linux, Termux | /proc and /sys (Termux: Android shows a shell its own processes; the cores' speeds instead of their times); a BSD: what its Linux /proc (linprocfs) shows | \n| Windows | one `NtQuerySystemInformation` for every process and the cores; the memory, the disks, the network, the battery | \n| macOS | `host_statistics64` ,`host_processor_info` ,`proc_pidinfo` | \n| Android app | its own process, the memory, the storage, the network, the battery (Android shows an app no other) | \n\nAlso: `endProcessTree` (a process and every one it started), `setPriority`,\n`startupItems` / `setStartupEnabled` (the programs that start with the\nsystem: freedesktop's autostart entries, Windows' Run keys turned off and\non as Task Manager does, a Mac's launch agents to look at),\n`connections` (TCP and UDP, with their programs: Linux, Termux and\nWindows), `appendHistory` / `loadHistory` (a point every few seconds on disk, the\nlast week).\n\n`liveGraph` (charts) is the graph of a task manager: black, a green grid\nthat moves with the line. It keeps its picture and moves it left a step\nfor each new value, drawing only the new part -- a frame costs a copy of\nthe picture whatever the number of values.\n\n`examples/taskman.nim` is **Retro Task Manager 1.0.2**: the processes with\ntheir share of the processor, the memory and the disk (search, sort by any\ncolumn, End task, End process tree, a priority, Open file location,\nProperties; Delete ends the one selected, after asking), the processor (and\neach core), the memory, the disks and the network as graphs that move, the\nprograms that start with the system (turned off and on), the network's\nconnections with their programs, and the last week's history; the update\nspeed (twice a second, every second, every 4 seconds, paused); on a phone,\na bottom bar. `nimble taskman` builds and runs it, `nimble taskmanapk`\nmakes its APK (about 1.8 MB).\n\n`retroui/photo` is an image editor's engine: documents of layers (opacity,\n29 blend modes from Multiply to Luminosity, Preserve Transparency, the\nBackground, layer masks, adjustment layers), a soft selection with\nfeathering, Grow, Border and Smooth,\nalpha channels, Quick Mask, Bézier paths, and many undos (only the 64-pixel\ntiles a change touched are kept). Its native files (.rpd) keep all of\nthat; PNG, JPEG and BMP are read and written (`openImage`, `saveFlat`), and\n`retroui/photofiles` reads and writes PSD and the other formats.\n`retroui/photopaint` paints on them: soft or hard brushes of any size,\nangle and roundness (or cut from the image), the airbrush, pencil,\neraser, rubber stamp, smudge, blur and sharpen, dodge, burn and sponge,\nthe paint bucket, gradients, lines with arrowheads, type. `retroui/photofx`\nhas over 45 filters (Gaussian Blur, Unsharp Mask, Motion Blur, Twirl,\nShear, Lens Flare, Emboss, Extrude, Crystallize...) and the color\nadjustments (Levels, Curves, Color Balance, Hue/Saturation by color range,\nSelective Color, Variations, Replace Color...), each with a preview.\n`retroui/photodevelop` makes a photo's everyday corrections in one go, as\ncamera raw programs do: white balance, exposure, contrast, highlights,\nshadows, whites, blacks, clarity, dehaze, vibrance, saturation, noise\nreduction, sharpening, vignette and grain, with Auto and presets; and\nAuto Tone / Contrast / Color, Shadows/Highlights, Vibrance, Exposure,\nPhoto Filter, Black & White and Channel Mixer. `retroui/photosmart` has the\nsmart tools, all worked out on the device: Content-Aware Fill, the\nhealing tools (Spot Healing, Healing Brush, Patch), red eye, Select\nSubject and Select Sky, Content-Aware Scale and enlarging with Preserve\nDetails. `retroui/photowarp` has Liquify, Lens Correction and Straighten.\nType layers stay editable: `retroui/textengine` sets styled text in any\nfont of the system (tracking, kerning, leading, scales, caps, super- and\nsubscript, underline; paragraphs aligned and justified, indented; Arabic\nand other right-to-left text; warps such as Arc, Flag and Fisheye; text\nalong a path), and `retroui/photostyles` gives layers their styles (drop\nand inner shadows, glows, bevel and emboss, satin, overlays, strokes).\n`retroui/photoshapes` makes shape layers (rectangles, rounded rectangles,\nellipses, polygons and stars, lines with arrowheads, 19 custom shapes, any\nSVG path; combined, subtracted, intersected; filled with a color or a\ngradient and stroked inside, centered or outside, solid or dashed) that\nstay sharp at any size, and layers can be clipped to the one under them.\n`retroui/photofiles` opens and saves PSD documents (.psd and .psb:\nlayers, groups, masks, vector masks, shapes, clipping, adjustment and fill\nlayers, layer styles) and exports to JPEG, PNG, PNG-8, GIF, TIFF, Targa\nand BMP with their options. `retroui/photoinks` models printing inks for\nthe CMYK mode, Proof Colors and the gamut warning, with Lab and duotones.\nBrushes have dynamics (jitter, scattering, flow, color) and symmetry, and\n`retroui/filterfactory` runs filters written as formulas, kept as plug-ins.\n`retroui/photowidgets` shows\na document (zoom 1:16 to 16:1, rulers, marching ants; drawn from smaller\ncopies when zoomed out) and has the editor's controls: the color picker,\nthe curves and levels editors, brush and swatch grids, layer rows.\n\nAll of it runs on every core (`retroui/parallel`): a 24 megapixel photo\nopens, blurs, sharpens and develops in about a second.\n\n``` python\nimport retroui/[photo, photopaint, photofx, photodevelop]\nlet d = openImage(\"cat.png\")\nd.setSelection(d.ellipseMask(80, 40, 360, 280))\nd.feather(8)\nd.beginStep(\"Hue/Saturation\")                    # one undo step\nd.applyFilter(0, hueSaturation(40, 25, 0, colorize = true))\nd.endStep()\nd.saveFlat(\"cat-sepia.jpg\", \"jpg\", 90)\n\n# a dull photo made right: Auto, then a little more of everything\nlet p = openImage(\"beach.jpg\")\nvar s = autoDevelop(Img(w: p.w, h: p.h, px: p.layer.px))\ns.clarity = 20\ns.dehaze = 10\np.applyFilter(developMargin(s), develop(s))\n```\n\n`examples/photo.nim` is **Retro Photo 4.9.7**, an image editor in the manner\nof the ones of 1994: the toolbox, the tabbed floating palettes (Brushes\nand Options; Picker, Swatches and Scratch; Layers, Channels and Paths;\nInfo; Commands; Navigator, History and Actions; Properties), an options\nbar, document windows, every menu of the time and its dialogs -- and\nwhat a photo editor has since: layer groups, Smart Objects and Smart\nFilters, Warp and Puppet Warp, the Blur Gallery, Select and Mask,\nContent-Aware tools, Photomerge, Merge to HDR, Sky Replacement, actions\nand Batch, color profiles, raw photos (DNG), saving in the background.\nSince 3.6: documents in tabs, the status bar of today's image editors,\nPreferences in pages, today's keys (or 1994's, or your\nown), brush presets in folders with Brush Settings and tips from 1 to\n5000 pixels, the standard preset files (.aco, .ase, .acv, .alv, .grd,\n.pat, .abr), 16 bits a channel, autosave and recovery, recent files and\nworkspaces. Since 3.7: the canvas and the window drawn by the GPU when\nthere is one, the transform box on the Move tool (type stays\ntype), held keys (Space, Ctrl, Alt), Place Into (a picture inside type or\na frame), gradient masks, the Type Mask tool, Remove Background, the Object\nFinder, Refine Hair, CMYK/Lab/Bitmap images from New, Color Settings and\nColor Libraries (.acb books). Since 3.8: today's toolbar (one column,\ngrouped tools that open when held, Shift+letter to go through a group, Edit\n\nToolbar), the Polygonal Lasso, and big photos that move, transform and\nadjust live (the view made from the layers at its own size while dragging).\nSince 4.0: the Perspective Crop, Background Eraser, Color Replacement,\nFreeform and Curvature pens, Star and Spiral shapes; Illustrator's path\noperations (Merge Shape Components, Offset Path, Outline Stroke, Simplify);\nNeural Filters; Save Adobe PDF for the web, print or a press (up to 3500\nppi, CMYK, marks and bleed, type and shapes as outlines) and PDF\nPresentation; PDF, Illustrator, PostScript and EPS files opened; Arabic set\non the canvas with kashida; Get Google Fonts; documents that come back at\nthe next start. Since 4.1: a faster engine (composite, open and save about\ntwice as fast), panels docked as in today's editors (`retroui/dock`), documents\nas tabs filling the workspace (windows of their own, MDI, as an option), a\nstatus bar under each picture, Adjustment Presets, Clarity, Dehaze and\nGrain, Layer Cleanup, Harmonize, Colorize, Content-Aware Expand, artboards,\nthe Frame tool, the Triangle, the Magnetic Lasso, Dynamic Text, RTL/LTR and\nkashida lengths, and the selection tools as today's (a drag inside moves\nthe outline). Since 4.2: the Filter Gallery (30 filters) with Oil Paint, Lighting\nEffects, Fibers, Displace and Smart Sharpen (`retroui/photoartistic`), 109\ncustom shapes in 12 sets with Live Shape properties, the Slice, Note and\nCount tools, and the toolbar docked at the left with the panels' header. Since 4.3:\n32 Bits/Channel (HDR: Radiance and PFM files, HDR Toning), press-ready PDF\n(PDF/X, printer's marks, bleed, type kept as editable text), Arabic\njustified by the scribes' kashida rules with the tashkeel kept, text in\ncolumns and flowing between boxes. Since 4.4: 16 bits a channel kept through filters,\nadjustments, transforms and the composite; CMYK by any ICC profile; OpenType shaping\n(FreeSerif built in), variable fonts, the OpenType, Character Styles and Paragraph Styles\npanels; editable type and Smart Objects in PSD, layered TIFF; WebP, OpenEXR, camera\nraw (NEF, ARW, CR2, ORF, RW2, PEF, RAF), HEIC/AVIF, SVG export; the Timeline (frame\nanimation, animated GIF and WebP); Rotate View, the Art History Brush, Vanishing Point;\nscripts (File > Scripts); a pen's pressure and tilt; filters on the GPU; the interface\nin Arabic, right to left. Since 4.5: File > New with presets and 23 templates, the\nAssets window (free photos, icons, SVG, textures from open sources; the Retro Photo\nLibrary of brushes, patterns, gradients, styles and designs), Quick Actions, Share,\nImage Processor, Contact Sheet, Crop and Straighten Photos, Match Color, Focus Area,\nBefore & After, QR codes, JPEG XL, SVG, CR3, layers composited on the GPU (the\nTimeline and video left to the toolkit: `photoanim`, `media`). Since 4.6: pictures too\nbig to edit whole open as a smaller copy or a part of them (read a band at a time),\nJPEG XL written lossy too, Pexels and Unsplash in the Assets window. Since 4.8: the Move\ntool as today's editors have it (handles that light up and take the pointer from further away, the\npointer showing what a drag does, a double-click on type to edit it, the layers under the\npointer on a right click, Auto-Select by Layer or Group), Smart Guides, the Selection\nBrush, the Adjustment Brush and Content-Aware Tracing (`retroui/edgetrace`), floating\nimages kept on top, the panels' side sized from its edge. Since 4.9: lines with arrowheads and other\nends that stay lines (their ends dragged with the Move tool), Image Size in pixels, percent, inches,\ncentimeters, millimeters, points or picas with a link for the proportions, Esc letting go of what is\nunder way, File > Save as PDF. `examples/magazine.nim` makes a three-page magazine with the\nengine, tool by tool.\n\n``` python\nimport retroui/[adobefiles, photobrushes]\nlet swatches = readAco(readFile(\"Pastels.aco\"))         # (name, color) pairs\nwriteFile(\"Pastels.ase\", writeAse(swatches))\nlet curves = readAcv(readFile(\"Contrast.acv\"))           # a curve a channel\nlet presets = defaultPresets()                           # 32 brushes in 4 folders\nlet pic = strokePreview(presets[0], 200, 40)             # as the Brushes palette shows it\npython\nimport retroui/[photo, photomerge, rawphoto, iccprofile]\nlet pano = photomerge(@[left, middle, right], amPerspective)   # layers and their blending masks\nlet hdr = exposureFusion(@[dark, normal, bright])\nlet raw = readDng(readFile(\"IMG_0042.dng\"))                  # light, white balanced\nlet img = developRaw(raw, Develop(exposure: 50))             # +0.5 stop, before 8 bits\nvar px = img.px\nconvertPixels(px, displayP3Profile(), srgbProfile())\npython\nimport retroui/[webp, gifanim, exr, core, lang_ar]\nvar frames: seq[tuple[px: seq[uint32]; delayMs: int]]\nfor (bmp, ms) in decodeWebpFrames(readFile(\"spin.webp\")): frames.add (bmp.px, ms)\nwriteFile(\"spin.gif\", encodeGifFrames(240, 240, frames))      # an animated GIF\nlet light = readExr(readFile(\"sky.exr\"))                      # floats, brighter than white\nsetUiLanguage(\"ar\", @toolkitArabic)                           # the toolkit in Arabic, right to left\n```\n\nRetro Photo 4.9 is laid out as today's image editors are, so their tutorials\nwork step by step, in a 1994 window: the menus have today's names and\nplaces (Image > Adjustments > Curves, Edit > Free Transform, Edit >\nTransform > Flip Horizontal, Layer > New > Layer, Select > Modify > Feather,\nFilter > Camera Raw Filter, View > Show > Grid), the panels are in Window A\nto Z (Adjustments, Brushes, Channels, Character, Clone Source, Color,\nGlyphs, Gradients, Histogram, History, Info, Layer Comps, Layers, Navigator,\nParagraph, Paths, Patterns, Properties, Shapes, Styles, Swatches, Tool\nPresets), the tools have their names and letters (the Ruler and Straighten\nLayer, the Color Sampler, the Magic Eraser among them), the keys are\ntoday's (Ctrl+T, Ctrl+J, Ctrl+Shift+N, Shift+F6, Ctrl+Alt+F...), and Free\nTransform keeps the proportions as it has since 2019. **Edit > Search**\n(Ctrl+F) finds any command, panel or tool by name; the **Contextual Task\nBar** under the image offers the next steps. With the Retro Photo 1994 key\nset (Preferences > Tools) the menus are 1994's.\n\n``` js\nlet ev = htmlView(readFile(\"help/index.html\"), baseDir = \"help\")   # framed, scrolls\nif ev.link.len > 0: discard openUrl(ev.link)\nhtmlBlock \"<b>Bold</b> and <span style='color:red'>red</span> in a card\"\nlet w = webView(\"https://nim-lang.org\", key = \"docs\", engine = weSystem)  # the system's browser\nif button(\"Back\"): webGoBack(\"docs\")\nfor m in w.messages: echo \"the page says \", m     # retro.post(text) in its scripts\n```\n\n`htmlView` shows a safe subset of HTML — headings, paragraphs, bold,\nitalic, underline, strike, code, marked and coloured text, links, lists,\nquotes, preformatted text, tables, pictures next to the page, definition\nlists — read by a forgiving parser. Scripts, styles, frames and forms are\nleft out, so no page can run code or reach the network. `htmlToMarkdown`\nconverts a page.\n\n`webView` is a web page, shown by one of two engines:\n\n- **The system's** (`engine = weSystem` , or`RETRO_WEB=system` for every\nview; the default in an Android app): the browser the system already has,\nplaced over the widget — WebView2 on Windows 10 and 11, WKWebView on\nmacOS and iOS, WebKitGTK on Linux and BSD (`libwebkit2gtk-4.1` , or 4.0,\nwhen it is installed), Android's WebView. Nothing is linked: it is loaded\nwhen the first page is shown, and without it the kit's engine stands in.\nScripts, forms and video work; links are followed in it, with`webGoBack` ,`webGoForward` ,`webReload` ,`webStop` and`webClose` .\n- **The kit's own** (`weKit` , and the default on desktops): local pages\n(`file://` , paths,`html:` text) drawn with`htmlView` , and pages on the\nweb get a button that opens them in the browser.\n\nThe system's engine talks to your app both ways, as Qt's web view does with its web channel, without a bridge object to set up:\n\n``` js\nvar ask = 0                                  # (beside your other state)\n\n# in the frame:\nwebAllow(\"help\", [\"nim-lang.org\"])           # other hosts: kept out, in page.blocked\nlet page = webView(\"help/index.html\", key = \"help\", engine = weSystem)\nfor m in page.messages: handle(m)            # the page called retro.post(text)\nif button(\"&Dark\"): webPost(\"help\", \"dark\")  # the page gets a \"retro\" event\nif button(\"&Count\"): ask = webRun(\"help\", \"document.querySelectorAll('h2').length\")\nfor r in page.results:                       # a later frame: its answer, as JSON\n  if r.id == ask: echo \"headings: \", r.value, r.error\nif button(\"&Larger\"): webZoom(\"help\", 1.25)  # text and pictures 125 %\n```\n\nIn the page: `retro.post(\"text\")` to the app, and\n`window.addEventListener(\"retro\", e => use(e.detail))` from it. The\n`WebEvent` also says the address, the title, how much is loaded, and\nwhether Back and Forward can go. While a menu or a dialog of the app is\nover it, the view steps aside and the widget shows a picture of the page\nas it last was, so the menu is drawn over the page as over any control;\na click in the app takes the keyboard back from it.\n\nThe page is part of the app, as a page in a browser is part of the browser:\n\n```\nwebAppFolder(getAppDir() / \"web\")            # the app's own pages, scripts, styles\nlet ui = webView(webAppUrl(\"index.html\"), key = \"ui\", engine = weSystem)  # app://local/...\nwebPermit(\"ui\", {wpCamera, wpMicrophone})    # what the page may use when it asks (none by default)\nfor p in ui.asked: echo \"the page wanted \", p # asked and not allowed\nfor d in ui.downloads: echo d.file, \" \", d.done   # into the Downloads folder\nif button(\"&Print\"): webPrint(\"ui\")          # the system's print dialog\n```\n\n`webAppUrl` gives the app's pages an address of their own\n(`app://local/`, `https://app.local/` on Windows,\n`https://appassets.androidplatform.net/app/` on Android), so they fetch\neach other as a site's files do, which a `file://` page may not. A page's\nfile box opens the system's file chooser. An Android app needs\nthe INTERNET permission for pages on the web (`retroapk --permission:INTERNET`). While you make the app, `RETRO_WEB_TOOLS=1` offers\nthe engine's developer tools (Inspect Element in the page's menu, F12 in\nWebView2, Safari's Web Inspector for WKWebView); without it they are off.\n\n``` js\nvar notes = readFile(\"README.md\")\nlet ev = markdownView(notes, baseDir = \".\")   # framed, scrolls\nif ev.link.len > 0: openUrl(ev.link)          # \"#heading\" links scroll by themselves\nif ev.task >= 0: toggleTask(notes, ev.task)   # a \"- [ ]\" box was clicked\nmarkdown \"**Inline** in a card or a page\"\n```\n\nHeadings (ATX and setext), paragraphs, **bold**, *italic*, ~~strike~~,\n`code`, links (inline, reference, `<auto>` and bare `www.`), images,\nfenced and indented code, block quotes (nested), bullet, numbered and\ntask lists (nested), tables with alignment, rules, hard breaks, escapes\nand entities. Right-to-left paragraphs are aligned right, with Latin\nwords and numbers kept in their order; a list item or a quote in Arabic\nhas its bullet, number or bar at the right, and a table in Arabic its\nfirst column at the right (a help page in Arabic reads right to left all\nthrough).\n\n``` js\nlet ding = decodeWav(readFile(\"ding.wav\"))       # WAV decoded by the kit\nif button(\"&Ding\"): discard playSound(ding)\ndiscard playSound(\"music.mp3\")                    # other formats: the system's player\ntone(880, 150)                                    # a sound made on the spot\nuiSound(usError)                                  # the toolkit's own sounds\nlet v = videoView(\"intro.mp4\", height = 240)      # play / pause, position, time\ndiscard videoView(\"https://example.com/live.m3u8\")  # web addresses and streams (HLS)\nanimation(spinner)        # spinner = loadGif(readFile(\"spinner.gif\"))\n```\n\nThe kit decodes WAV and makes tones itself and plays them through the system's own audio, loaded only when the first sound plays; videos and the other formats are played by the system's own player, and the kit draws the video's pictures in the window itself, under its menus and dialogs:\n\n| Platform | WAV, tones | MP3, OGG, AAC… | Video ( `videoView` ) | \n|---|---|---|---|\n| Linux, BSD, Termux:X11 | PulseAudio or ALSA | GStreamer, when installed | in the window (GStreamer) | \n| Windows | waveOut | the system (MCI) | in the window (Media Foundation) | \n| macOS, iOS | AudioQueue | AVAudioPlayer | in the window (AVFoundation) | \n| Android | MediaPlayer | MediaPlayer | in the window (VideoView) | \n\nWithout a sound device, or a format the system cannot play, sounds are\nquietly not heard; nothing waits or crashes. `videoInfo`, `videoFrameAt`\nand `videoFrames` read a video's pictures; `beginVideo`, `addFrame` and\n`finish` write an MP4.\n\n**The system's media controls** (`retroui/mediasession`): what the app\nplays shows on the lock screen, the notification shade, the taskbar's\nmedia flyout, the menu bar's Now Playing or the desktop's media applet,\nand their buttons and the keyboard's media keys come back to the app --\nMPRIS on Linux and the BSDs, the System Media Transport Controls on\nWindows, MPNowPlayingInfoCenter on macOS and iOS, a MediaSession on\nAndroid:\n\n```\nsetNowPlaying(NowPlaying(title: song.title, artist: song.artist, playing: playing,\n                         positionMs: pos, durationMs: dur, canNext: true))\nfor k in mediaKeys():\n  case k.key\n  of mkPlayPause: playing = not playing\n  of mkNext: nextSong()\n  else: discard\n```\n\n`retroui/capture` takes the camera and the microphone through each\nsystem's own API, loaded when first used: a live picture in the window\n(drawn by the kit, under its menus and dialogs), photos, videos of the\ncamera, sound recorded to a file.\n\n``` js\nlet cam = cameraView(height = 420)                  # cameras() lists them\nif button(\"&Photo\"): cameraPhoto(\"\", \"photo.jpg\")\nif button(if cam.recording: \"&Stop\" else: \"&Record\"):\n  if cam.recording: cameraStop() else: cameraRecord(\"\", \"clip.mp4\")\nif button(\"&Voice\"):\n  if recordingSound(): discard stopRecordingSound() else: discard recordSound(\"note.wav\")\nprogress(soundLevel())\n```\n\n| Platform | Camera | Microphone | \n|---|---|---|\n| Linux, BSD | Video4Linux (YUYV, MJPEG) | PulseAudio (or PipeWire's), else ALSA | \n| Windows | Media Foundation | waveIn | \n| macOS, iOS | AVFoundation | AVAudioRecorder | \n| Android app | Camera2, its preview over the widget | MediaRecorder | \n| Termux | Termux:API (photos) | Termux:API | \n\nPhones ask the user the first time: an Android app needs `retroapk --permission:CAMERA --permission:RECORD_AUDIO`; `retroios` and `retrokit mac` put the usage texts in Info.plist.\n\n`window` opens a window of the system's own beside the app's -- a tool\npalette, a second view for another monitor, a preview -- laid out and\ndrawn as the app's own is, with its own menus and dialogs, at the place in\nthe frame the app draws it:\n\n``` js\nvar toolsOpen = true\napp \"Editor\", 800, 600:\n  if menuItem(\"&Tools\"): toolsOpen = true\n  window(toolsOpen, \"Tools\", 240, 360):     # its close button sets toolsOpen to false\n    if button(\"&Bold\"): bold = not bold\n  textArea(doc)\n```\n\nIt closes when it is no longer drawn; `inWindow` says whether the code\nruns inside one, and `Event.window` which window an event came from. X11,\nWayland, WinAPI and Cocoa open real windows; where the system has one\nwindow (phones, the browser, a screen without a desktop) it is a dialog\nover the app. Where the system draws no title bar (phones, Wayland\nwithout server-side frames, the screen driver), `captionBar` draws the\n3.1 one.\n\nNotifications, sharing, the place, speech, a tap felt, starting with the\ncomputer, the battery, the device: `retroui/system` asks each system for\nits own, with the same calls on Linux, Windows, macOS, Android, iOS and\nin a web page. Nothing waits on the system (an answer that comes later is\nasked again next frame), and \"not here\" is an answer, never an error:\nthe app offers what it can instead. **[docs/system.md](https://github.com/foxoman/retroui/blob/main/docs/system.md)**\nsays what every call does on every system, what the app declares for it\n(Android permissions, Info.plist, https) and how to write it so it works\neverywhere.\n\n``` python\nimport retroui, retroui/system\nvar askPlace = false\napp \"Shop\", 420, 300:\n  if button(\"&Notify\"):\n    if not notify(\"Order 1042\", \"Ready at the counter\", tag = \"orders\"):\n      toast(\"Order 1042 is ready\")                       # no system notifications here\n  if button(\"&Share\"):\n    if not share(text = \"Order 1042 is ready\", url = \"https://example.com/o/1042\"):\n      putClipboardText(\"https://example.com/o/1042\")     # no share sheet: copied\n  if button(\"&Where am I?\"): askPlace = true\n  if askPlace:\n    let p = location()                                   # psWaiting until the first fix\n    if p.state == psOk: label \"At \" & $p.lat & \", \" & $p.lon\n  if button(\"S&peak\"): speak(\"Your order is ready\")\n  if button(\"&Tap\"): haptic(hSuccess)\n  let d = deviceInfo()                                   # name, system, language, battery, dark, online\n  label d.name & \", \" & d.system\n```\n\n|  | Linux | Windows | macOS | Android | iOS | Web | \n|---|---|---|---|---|---|---|\n| `notify` ,`setBadge` | ✓ | ✓ | ✓ | ✓ | – | ✓ | \n| `share` | – | – | ✓ | ✓ | ✓ | ✓ | \n| `location` | – | – | ✓ | ✓ | ✓ | ✓ | \n| `speak` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | \n| `haptic` ,`vibrate` | – | – | ✓ | ✓ | ✓ | ✓ | \n| `setOpenAtLogin` | ✓ | ✓ | ✓ | – | – | – | \n| `requestAttention` | ✓ | ✓ | ✓ | – | – | ✓ | \n| camera, microphone, media keys, secrets, signing in | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | \n\nThe Gallery's System page tries each one.\n\nRetro never stands between an app and its system: `retroui/native` reaches\n**any** OS API from Nim, with nothing to install and no second language to\nwrite.\n\n``` python\nimport retroui\necho deviceName(), \", \", osVersion(), \", battery \", batteryLevel(), \"%\"\nif systemDarkMode(): ui.theme = Dark\nvibrate(40); keepScreenOn(true)\n\n# Any C library, on any system\nlet lib = nativeLib(\"libm.so.6\")                    # .so, .dll, .dylib, a framework\nlet cbrt = nativeProc[proc (x: cdouble): cdouble {.cdecl.}](lib, \"cbrt\")\necho cbrt(27.0)\n\nwhen defined(android):                              # any Java API, by reflection\n  echo javaStatic(\"android.os.Build\", \"MODEL\")\n  echo javaActivity(\"getPackageName\")\nwhen defined(macosx) or defined(ios):               # any Objective-C API\n  let info = objcSend(ObjcId, objcClass(\"NSProcessInfo\"), \"processInfo\")\n  echo toNim(objcSend(ObjcId, info, \"hostName\"))\nwhen defined(windows):                              # Win32 and COM\n  echo windowsDocumentsFolder(); taskbarProgress(0.4)\n```\n\n|  | How | \n|---|---|\n| any system | `nativeLib` ,`nativeProc` : a typed proc of any function in any shared library | \n| Android | `javaStatic` ,`javaActivity` ,`javaCall` ,`javaNew` ,`javaRelease` ,`onUiThread` : any Java API, no JNI or Java to write | \n| macOS, iOS | `objcClass` ,`sel` ,`objcSend` ,`nsString` ,`toNim` ,`autoreleasePool` | \n| Windows | typed Win32 helpers and COM ( `taskbarProgress` ); anything else through`nativeProc` | \n| Linux, Termux | `termuxApi` (Termux:API commands), any library through`nativeLib` | \n| everywhere | `vibrate` ,`deviceName` ,`osVersion` ,`batteryLevel` ,`systemDarkMode` ,`beepSystem` ,`userName` ,`keepScreenOn` ,`openSettings` — neutral answers where a system has no such thing; they never raise | \n\nNim also compiles to C, so `{.importc.}` and C headers work as in any Nim\nprogram; `examples/native.nim` shows each platform.\n\n`retroui/update`: the app reads the update file you publish, checks its\nsignature (Ed25519) with the public key built into it, downloads its file\nfor the system, checks its SHA-256, and installs it -- an AppImage\nreplaced, a Windows program or installer, a package handed to the\nsystem's installer, a web page's newest build. Nothing comes from anyone\nwithout your key.\n\n``` python\nimport retroui/update\nconst updateKey = \"01VFThQl2gEEkWYqnvsvw+bbRTD59hQ2SAdg6kBwDjU=\"   # retrokit update-key printed it\nlet u = checkForUpdate(\"https://example.com/notes/update.json\", updateKey, \"1.2.0\")\nif u.available:\n  let file = downloadUpdate(u)        # raises UpdateError when it is not the signed file\n  if installUpdate(file): restartApp()\nretrokit update-key                   # once: update-key.secret (yours alone) and the public key\nretrokit update-sign update.json      # each release: sizes and SHA-256s filled in, update.json.sig\njs\nlet dir = dataDir(\"myapp\")         # a folder the app may write to (any platform)\nvar s = loadSettings(\"myapp\")       # key = value, saved safely\ns.put(\"theme\", 2)\nstoreAppearance(s)                  # the look the user chose (restoreAppearance)\nsaveSettings(\"myapp\", s)\nopenUrl(\"https://nim-lang.org\")     # browser, mail, maps...\nopenFile(path); shareFile(path)     # the system's app for it; Android's share sheet\nfor (name, path) in places(): ...   # Home, Documents, Downloads, SD cards, drives\nif not storageAccess(): requestStorageAccess()   # Android \"All files access\"\nif isMobile(): ...                  # platformName(): \"android\", \"ios\", \"windows\"...\n```\n\nThe clipboard works everywhere (`getClipboardText`, `putClipboardText`,\n`Ctrl+C`/` Ctrl+V`, `Cmd` on iPad keyboards). `closeRequested()` /\n`cancelClose()` catch the window's close button, Alt+F4 and, on Android,\nthe Back button.\n\n`retroapk` compiles the app into `libmain.so` for Android's\n`NativeActivity` and writes the APK itself: binary manifest, resource\ntable, icon, zip and APK Signature Scheme v2 signature. The Android side\nis one C file in the driver plus a small Java activity that comes already\ncompiled inside the tool (soft-keyboard text, system-bar colors and\ninsets, opening and sharing files, decoding pictures, storage access) --\nyou never need Java or Gradle. The parts that need Android's own\nclasses come compiled the same way: the web view, sound and video,\nprinting, and the reflection calls of `retroui/native`.\n\n**On the phone or tablet, in Termux:**\n\n```\npkg install nim clang ndk-sysroot openssl-tool git\nnimble install retroui\nretroapk ledger.nim --name:Ledger --install    # the installer asks to confirm\n```\n\n**On a desktop** -- Windows, macOS, Linux or a BSD -- with a clang that has\nlld, or zig: no NDK (`retroapk` carries Android's headers and libraries;\nan NDK in `ANDROID_NDK_HOME` is used when it is there, and fetched once when\nthere is no compiler at all). `adb` installs and starts the app:\n\n```\nretroapk app.nim --name:\"My App\" --id:com.me.myapp --run\nretroapk app.nim --abi:arm64-v8a,x86_64     # also the emulator (32-bit ABIs: with the NDK)\n```\n\n**Permissions:** `retroapk` reads what the app's compiled code uses -- the\ncamera, the microphone, notifications, the place, a buzz, the network --\nand lists those permissions by itself (it says which; the hardware they\nimply is not required, so the app still installs where there is none).\nThe app asks the user the first time the feature is used, and goes on\nwithout it when the answer is no.\n\n**When an app stops:** a crash of its Nim code, of its native code (a\nsignal) or of Java is written to its fault log; the next start shows it\nfirst -- before the app's own code runs, so a crash at the start shows too\n-- with Copy and Send by Email, and \"Start without what was open the last\ntime\" (`startFresh`: Files, Photo and Board leave their last folders,\npictures and board).\n\n**On a phone or tablet,** a build runs as many C compilers as the memory\nallows, at most four in Termux (Android stops builds with too many:\n\"signal 9\"; BUILDING.md has the setting that turns that off).\n\nOther options: `--icon:icon.png`, `--version:1.2 --code:3`, `--jobs:N`,\n`--orientation:portrait`, `--permission:INTERNET`, `--storage` (a file\nmanager; it may also see Termux), `--query:com.termux` (another app this\none looks for), `--debuggable`, `--key:key.pem --cert:cert.der` (else a debug\nkey is made once in `~/.retro/android`; keep it: updates must be signed\nwith the same key). `echo` output goes to `adb logcat -s retro`.\n\nFor Google Play, `--aab` (or `retrokit aab`) writes an Android App Bundle\ninstead: the manifest and resources in aapt2's proto formats, the\nlibraries of each ABI, signed with your upload key (`--key`, `--cert`);\nPlay makes the APKs for each phone from it, with the libraries\nuncompressed on 16 KB pages.\n\nOn Android the app gets the touch metrics, the window's real density, its own navy title bar under a matching status bar, safe margins, the soft keyboard while a text field has the focus, Back as Esc, pause and resume.\n\nOn a Mac with Xcode:\n\n```\nretroios app.nim --simulator --run                      # in the Simulator\nretroios app.nim --sign:\"Apple Development: Me (TEAMID)\" \\\n           --profile:dev.mobileprovision --device:\"My iPad\" --run\n```\n\nThe driver is UIKit in one Objective-C file: the frame is the view's\nlayer image, touches and the keyboard (`UIKeyInput`, hardware keys with\nCmd as Ctrl) come in. Without a Mac, `retroios app.nim --sdk:iPhoneOS.sdk`\ncross-links an unsigned bundle with `lld`. `--ipa` also writes the `.ipa`\n(`Payload/<Name>.app`, zipped) that Transporter and TestFlight take; for\nthe App Store, sign with your distribution certificate and profile.\n\n`retrokit web app.nim` makes the app a web page, with nothing but Nim and\nclang: Nim's C becomes WebAssembly by clang and wasm-ld alone -- no\nEmscripten, no WASI SDK -- with the kit's own small C library and page\nscript, carried inside the tool. The same app, the same code, the same\npixels:\n\n```\nretrokit web ledger.nim --files:data --icon:ledger.png    # build/web: index.html, app.wasm, retro.js, ...\nretrokit run --web --watch ledger.nim                     # served on localhost, built again on every save\n```\n\nThe page installs (a manifest, icons) and works offline (a service worker\nkeeps it); the app's files are at `/app`, and what it writes in its home\nfolder stays in the browser (IndexedDB) for the next visit. Input is the\nbrowser's: input methods, dead keys, paste, the pen, touch, the wheel;\ndrops land in `/tmp/dropped`; sound plays through an AudioWorklet, video\nthrough the browser's own; `httpRequest` reaches the page's own site with\na short address, other sites when they allow the page (CORS). A web view\nis the page's own frame. What the app writes in `/downloads` is handed to\nthe user (the browser's Save As), Open is the browser's file chooser,\nprinting is the browser's print dialog. The camera and the microphone,\nthe media keys, notifications, sharing, the place, secrets (sealed by a\nkey the browser keeps), signing in (a pop-up back to `oauth.html`), chat\n(XMPP over a WebSocket), the 3D view on the GPU (WebGL 2) and screen\nreaders (the controls mirrored as ARIA) work in a page too.\n\n```\nretrokit web app.nim --threads        # and a build with threads, taken where the browser allows it\nretrokit web mail.nim --threads-only  # an app that needs them: Retro Mail in a page\n```\n\n**Threads:** with `--threads` the kit's drawing, pictures and maps run on\nevery core, and the app's own `createThread` works: Web Workers on one\nmemory, real locks. The page keeps itself apart from other sites\n(crossOriginIsolated) on any server through its service worker; a browser\nthat will not runs the build without threads. **Small:** SIMD,\nlink-time optimization, the fonts beside the page (fetched when used), a\ngzipped copy; `--small` for the least. What a page cannot do: other\nprograms and raw sockets. `tests/web/run.sh` checks it all in a real\nChromium (at 100 % and 200 %, with and without threads), and\n`tests/web/node.sh` runs the headless tests as WebAssembly under Node.\n\nA kiosk, a till, a machine's panel, a Raspberry Pi with no desktop\ninstalled: the screen driver draws straight to the Linux screen (DRM/KMS,\nor the framebuffer `/dev/fb0` where there is no DRM) and reads the\nkeyboard, mouse, touch screen, touch pad and pen itself (evdev). Started\non a text console with no X11 or Wayland, an app takes it by itself;\n`RETRO_DRIVER=kms` (or `fb`) asks for it, `-d:kiosk` builds an app with\nonly it.\n\nIt takes the screen's own mode (`RETRO_KMS_MODE=1280x800` another), the\nkeyboard's layout from the system's settings, and the scale from the\nscreen's size in millimetres; Ctrl+Alt+F1..F12 switch consoles and give\nthe screen back, Ctrl+Alt+Backspace closes the app. `tests/kiosk/run.sh`\nchecks it with a file as the screen and pipes as the devices.\n\nWhat each store takes is made in Nim, on any system: no Android SDK, no Windows SDK, no Xcode (except where Apple's own tools must sign).\n\n| Store | Command | What it makes | \n|---|---|---|\n| Google Play | `retrokit aab app.nim --key:upload.pem --cert:upload.der` | an Android App Bundle (aapt2's proto formats, JAR-signed with your upload key); Play makes the APKs | \n| Android, side-loaded | `retrokit apk app.nim` | an APK, signed (APK signature v2) | \n| Microsoft Store, Windows | `retrokit msix app.exe --name:Company.App --version:1.2.0.0 --key:k.pem --cert:c.pem --timestamp:URL` | an MSIX (its manifest, block map, logos, signature) with the program inside signed too | \n| Windows, any | `retrokit sign app.exe --key:k.pem --cert:c.pem --timestamp:URL` | the program signed (Authenticode, SHA-256, an RFC 3161 time stamp) | \n| macOS, outside the store | `retrokit mac app --sign:\"Developer ID Application: ...\" --notarize:PROFILE` | an `.app` (Info.plist, the icon as`.icns` ), signed with the hardened runtime, notarized and stapled (on a Mac), zipped | \n| macOS, to try | `retrokit mac app` | the `.app` signed ad hoc, on whatever system builds it (Apple silicon runs it; a downloaded copy asks once) | \n| App Store, TestFlight | `retroios app.nim --sign:... --profile:... --ipa` (or`retrokit ipa App.app` ) | the `.ipa` | \n| The web | `retrokit web app.nim` | a page that installs and works offline | \n\nWithout `--key` and `--cert` a test key is made once (`~/.retro/android`,\n`~/.retro/windows`): good to try a package, never to publish. A key from a\n`.pfx` is two `openssl pkcs12` commands away (`retrokit sign --help`). Each\npackage is checked with the tools of others: `jarsigner` and `bundletool`\nfor the bundle, `osslsigncode` for the program and the MSIX (all its\ndigests), `rcodesign` for the Mac's signature -- and on GitHub, by\nWindows itself (`Get-AuthenticodeSignature`, `signtool verify`, the MSIX\ninstalled and removed) and by macOS (`codesign --verify --deep --strict`).\n\n| Target | Driver | Build | Checked | \n|---|---|---|---|\n| Linux / BSD, X11 | X11 (Xlib loaded at run time) | `nim c -d:release app.nim` | run, screenshots, every test; GitHub: the live check under Xvfb with WebKitGTK | \n| Linux / BSD, Wayland | Wayland, the protocol spoken by the kit (no libwayland) | the same build ( `RETRO_DRIVER=wayland` , or by itself with no X11) or`-d:wayland` | sway with no screen, 100 % and 150 % (here and on GitHub); weston by hand | \n| Linux, no desktop | the screen driver: DRM/KMS, else the framebuffer; evdev input | the same build (on a console, or `RETRO_DRIVER=kms` ), or`-d:kiosk` | a file as the screen and pipes as the devices, three sizes and depths (here and on GitHub); not yet on a real DRM screen | \n| Windows | WinAPI (GDI) | `nim c -d:release --app:gui app.nim` | Wine; GitHub: the portable tests, the live check (WebView2), a program signed and an MSIX installed by Windows | \n| Windows from Linux | WinAPI | `nim c -d:release -d:mingw --cpu:amd64 --app:gui --passL:-static app.nim` | built | \n| macOS | Cocoa | `nim c -d:release app.nim` (`retrokit mac` for the`.app` ) | GitHub: the portable tests, the live check (WKWebView), an `.app` signed in Nim checked by`codesign` and run | \n| Android 7+ | NativeActivity | `retroapk app.nim` (`--aab` for Google Play) | built, signed, verified with `apksigner` ,`aapt` ,`jarsigner` and`bundletool` ; built without the NDK (clang + lld, zig); run on a tablet; the driver against a simulated Android; GitHub: the live check in the emulator | \n| iOS 14+ | UIKit | `retroios app.nim` (`--ipa` for the App Store) | linked against the iOS SDK; GitHub: the live check in the Simulator | \n| The web | WebAssembly (clang and wasm-ld, the kit's own C library) | `retrokit web app.nim` | Chromium (Playwright), 100 % and 200 %, here and on GitHub | \n| anywhere | SDL3 | `nim c -d:release -d:sdl3 app.nim` (needs the`sdl3` wrapper package) | built | \n\n\"GitHub\" is the *Every system* workflow (`.github/workflows/systems.yml`),\nrun on a push to `main`; the README says what it runs, its\nresults are in each run's log and pictures.\n\nWidgets record small drawing commands, each with a hash. At the end of a\nframe the screen is cut into tiles 64 pixels wide and 32 high: an\nunchanged frame costs no drawing and no driver call at all, and otherwise\nonly the tiles whose commands changed are drawn again and sent to the\nscreen (with the GPU, only they are drawn, in a few rectangles). The\ntiles are drawn straight into the window's own memory where the system\nlends it (shared with the X server on X11, a DIB section on Windows), and\ndrawing the controls makes no string, list or closure: a frame of the kit\ntakes no memory at all. A small\nchange is drawn on the app's own thread; a big one on all the cores. When\nnothing happens the app sleeps: no frame, no CPU, until input or a timer.\nGlyphs are rendered once per size and kept; maps, 3D scenes, canvases and\nreport pages are drawn into pictures of their own only when they change;\nslow work (listing folders, decoding pictures) runs on worker threads\n(`retroui/background`).\n\nMeasured on Linux (`nimble bench`, library cost without the OS: a change\nand the frames until it is on the screen; a small, shared 2-core cloud\nmachine, so the numbers wander by a third):\n\n|  | per change | memory taken | \n|---|---|---|\n| full redraw, 800×600, ~60 widgets | 0.24 ms | none | \n| nothing changed | 0.04 ms, nothing sent | none | \n| one value changed | 0.08 ms, only its tiles drawn and sent | none | \n| the pointer onto a tool button (lit) | 0.09 ms | none | \n| a letter typed in a text box | 0.13 ms (two frames) | 6 allocations (the text) | \n| 100,000-row grid, tree and chart, full redraw | 0.26 ms | the cells' strings the app gives | \n| scrolling that grid with the wheel | 0.28 ms (two frames) | the same | \n| a folder of 50,000 files: details / icons, full redraw | 0.36 / 0.49 ms | its names and sizes as text | \n| ... scrolling it with the wheel | 0.45 / 0.63 ms | the same | \n\n|  | once | \n|---|---|\n| sort 50,000 names naturally | 16 ms | \n| decode a 2000×1500 PNG | 65 ms | \n| decode a 12 MP JPEG (baseline / progressive) | 330 / 780 ms | \n| ... its thumbnail (1/8, the blocks' averages) | 40 ms | \n| the world map at a new zoom | 32 ms (then nothing until it changes) | \n| a 106-page table report laid out and written as PDF | 0.15–0.3 s | \n| 1,000 filled and stroked canvas shapes, 800×600 | 15 ms | \n| a street map's vector tile decoded and drawn, 512 px | 7 ms | \n| a glTF model read (the tests' car) / drawn lit on the CPU, 800×600 | 0.4 / 11 ms | \n| a 4 MB program signed (Authenticode) / made into a signed MSIX | 80 / 75 ms | \n\nJPEG colours are scaled up smoothly as libjpeg does (within 3 levels of it on every pixel); on Android and iOS the system decodes pictures.\n\n**Sizes** (Linux x86-64, `retrokit small`: optimized for size, LTO,\nstripped):\n\n| App | Size | \n|---|---|\n| `examples/hello.nim` | 1.9 MB | \n| `examples/ledger.nim` (grids, forms, CSV) | 2.2 MB | \n| `examples/taskman.nim` (Retro Task Manager) | 2.3 MB | \n| `examples/charts.nim` (Chart Viewer) | 2.5 MB | \n| `examples/notepad.nim` (with the file dialogs) | 2.5 MB | \n| `examples/invoice.nim` (templates, reports, PDF, printing) | 2.6 MB | \n| `examples/communicator.nim` (chat, end-to-end encryption) | 2.8 MB | \n| `examples/hrbook.nim` (HR Book) | 3.4 MB | \n| `examples/retroboard.nim` (the whiteboard) | 3.7 MB | \n| `examples/filemanager.nim` (Retro Files) | 4.3 MB | \n| `examples/gallery.nim` (everything: street map, 3D models, canvas, PDF, HTML, media) | 5.7 MB | \n| `examples/mail.nim` (Retro Mail) | 6.0 MB | \n| `examples/photo.nim` (Retro Photo) | 10.6 MB | \n\nOnly what an app calls is linked: an app without the map, the 3D view or PDF does not carry them. The fonts (FreeSans, FreeSerif and FreeMono in four styles each) are about 1 MB of every app, the Nerd icons 52 KB and the 255 pixel icons 24 KB, all stored compressed and unpacked when first used.\n\n**Beside other toolkits.** `tools/compare/compare.sh` builds the same\nHello -- a label, a text box, two buttons -- with egui 0.37, iced 0.14 and\nSlint 1.18 (release builds, stripped) and measures each the same way on an\nX screen of its own (Xvfb 1920×1080, no window manager, no graphics card:\nOpenGL is Mesa's llvmpipe, as in a VM, a remote desktop or Termux:X11).\nResizing at 1080p draws every pixel again; CPU is per step, counted in\n10 ms ticks, so the small numbers are within a tenth of a millisecond:\n\n|  | file | memory idle | at 1080p | threads | CPU idle | a resize at 1080p | a hover | \n|---|---|---|---|---|---|---|---|\n| **Retro Nim UI** | **1.9 MB** | **7.1 MB** | **14.6 MB** | **1** | 0 % | 1.8 ms | **0.2 ms** | \n| Retro Nim UI, no shared memory |  | 7.2 MB | 14.9 MB | 1 | 0 % | 3.8 ms | 0.15 ms | \n| Retro Nim UI, OpenGL |  | 107 MB | 115 MB | 6 | 0 % | 12 ms | 0.5 ms | \n| egui (OpenGL) | 20.2 MB | 122 MB | 153 MB | 8 | 0 % | 46 ms | 88 ms | \n| iced (wgpu) | 15.5 MB | 120 MB | 142 MB | 12 | 0 % | 33 ms | 16 ms | \n| iced (tiny-skia, CPU) |  | 15.1 MB | 26.4 MB | 6 | 0 % | 2.2 ms | 0.45 ms | \n| Slint (OpenGL) | 21.6 MB | 121 MB | 151 MB | 8 | 0 % | 16 ms | 13 ms | \n| Slint (software) |  | 19.9 MB | 27.5 MB | 3 | 0 % | 1.7 ms | 0.6 ms | \n\nOn a software OpenGL every GPU path pays for Mesa's own memory and threads; \"automatic\", the kit's default, draws on the CPU there and with the GPU when the GPU is a real one. With a real graphics card the GPU paths of all four are faster than here, and the kit's CPU path is what it is here.\n\n- **Every decoder and parser checks what it reads:** PNG, BMP, JPEG and\nGIF headers, Huffman tables and coefficient ranges, deflate streams, zip\ndirectories (ZIP64 too) and dates, WAV chunks, CSV, OBJ, STL and glTF\nmodels (every index, kind and size of the JSON, every accessor inside its\nbuffer), vector tiles and PMTiles archives, the map services' answers\n(JSON of any shape), Markdown and HTML nesting (deeper than 24 levels is\ntext), report templates, certificates, time-stamping answers, and the\nWindows and macOS programs it signs. A damaged or hostile file is refused\nwith an error, never a crash.\n- **Limits on size:** pictures larger than`maxPicturePixels` (120 million)\nare refused, inflating stops at the size the file claims (a \"zip bomb\"\ncannot use up the memory), models, sounds and templates have limits of\ntheir own, and zip names that would climb out of their folder (\"../\")\nare refused.\n- **Pages cannot run code:**`htmlView` leaves out scripts, styles, frames\nand forms and never reaches the network; report templates only read the\ndata they are given.\n- **Fuzzed:**`tests/fuzz/fuzz.nim` feeds hundreds of thousands of damaged\nfiles to every decoder;`fuzzui.nim` drives the widgets with random\nUnicode (Arabic, marks, bidi controls, broken UTF-8), keys, clicks,\ntouches and hostile data — the explorer parts, the grids with every cell\nkind, masks, property and tree grids, MDI windows, find, the planning\nviews, the map, the 3D view and HTML;`fuzz_charts` ,`fuzz_htmlview` ,`fuzz_view3d` ,`fuzz_media` ,`fuzz_canvas2d` ,`fuzz_report` ,`fuzz_models` (glTF: damaged bytes, and JSON with its numbers, kinds and\nindices changed, then played and drawn),`fuzz_maps` (vector tiles,\nPMTiles, the answers of all 13 services, polylines) and`fuzz_store` (certificates, time stamps, Windows and macOS programs, zip archives and\nMSIX packages) do the same for their modules. They found real bugs, now\nfixed and kept as tests. Every night the decoders also run under\nlibFuzzer (`tests/fuzz/libfuzz.nim` : coverage-guided, with the\nsanitizers).\n- **32-bit:**`tests/run32.sh` runs the tests as 32-bit programs, where an`int` is as small as WebAssembly's: a number that only fits in 64 bits\n(a checksum, a color's weighted sum, a file's size) shows up there.\n- **Memory checked:** the tests also run under AddressSanitizer, UBSan\nand LeakSanitizer (`--cc:clang -d:useMalloc --passC:\"-fsanitize=address,undefined\" --passL:\"-fsanitize=address,undefined\"` ,`LSAN_OPTIONS=suppressions=tests/lsan.supp` for what Nim's runtime keeps\nfor each thread), without a report.`tools/returncheck.py` finds the\nshape of code Nim 2.2 leaks with (`return x` after`result` was set):\nnone in the kit or the apps.\n- **Threads:** work on worker threads never shares state with the UI\nthread except through a locked queue; results are handed over at the\nstart of a frame. A file that changes or shrinks while it is shown (a\nlog rotated) is read safely.\n- **Updates only from you:**`retroui/update` installs nothing that your\nkey did not sign (Ed25519), and only the download whose SHA-256 was signed.\n- **Files written safely:** settings, PDFs and pictures are written to a\ntemporary file and moved into place; file jobs leave no half-written\nfile when cancelled and never follow links out of a folder they delete.\n- **No exceptions out of the UI:** the widgets clamp what they are given\n(negative sizes, NaN, empty lists, a selection past the end) instead of\nraising.\n\nThe tests swap the screen for a fake driver that plays back scripted\nmouse, keyboard and touch input and keeps the pixels, so no window is\nneeded. `nimble test` runs them all; `nimble fuzz` runs the fuzzers.\n`sh tools/cicheck.sh` runs what GitHub's CI runs, here, and\n`sh tools/warncheck.sh` checks that the kit's own files build without a\nwarning as every system builds them (Linux, Termux, an Android app,\nWindows, macOS, iOS, the web with threads and without), and the examples\nand the tests too.\n\n**Every app on Android's emulator:** the systems workflow builds the main\napps for the emulator (`nimble emuapks`) and `tests/android/apps.sh` opens\neach on Android 11 and 15 -- with nothing allowed, then with every\npermission -- drives it with the monkey (random taps and keys, the same\neach time), and fails on a crash of Java or of the native code, with the\nscreens, the log and the app's own fault log kept.\n\n**On every system, with its own drivers.** `tests/live/livecheck.nim` opens\na real window and goes through pages of the guide by itself -- controls, a\ngrid, a chart, HTML, an open menu, a dialog -- saving each frame, then\nshows a page in the system's browser engine and talks to it (`webRun`,\n`webPost`, `retro.post`), and writes `results.txt`. GitHub Actions\n(`.github/workflows/systems.yml`, on a push to `main`) runs it on\nLinux (Xvfb, WebKitGTK), Windows (WebView2), macOS (WKWebView), the iOS\nSimulator and the Android emulator, with the tests made for every system\non Windows and macOS; `tools/live/compare.nim` checks every system's\nframes against `tests/live/ref`, made on Linux: the kit draws the same\npixels everywhere. The pictures, the results and the differences (in red)\nare kept with each run. After a change to how one of those pages draws,\n`sh tools/live/makeref.sh` makes the reference again.\n\nThe same workflow checks what has no window of its own to test with a\nfake: the Wayland driver on sway with no screen (`tests/wayland/run.sh`,\n100 % and 150 %), the screen driver with a file as the screen\n(`tests/kiosk/run.sh`), the 3D view on the GPU against the CPU\n(`tests/gpu3d`), an app as a web page in Chromium (` tests/web/run.sh`), the\nheadless tests as 32-bit programs (`tests/run32.sh`: an `int` as small as\nWebAssembly's), and the store packages with the tools of others and the\nsystems themselves (`tests/store`).\n\nEvery night (`.github/workflows/nightly.yml`) the headless tests run under\nAddressSanitizer and UBSan, the fuzzers run for long under them, the\ndecoders run under libFuzzer (`tests/fuzz/libfuzz.nim`), the tests run as\n32-bit programs under UBSan and as WebAssembly under Node\n(`tests/web/node.sh`). `.github/workflows/devices.yml` runs the live check\non real phones (Firebase Test Lab) and on a Raspberry Pi when they are set\nup; on Windows, UI Automation reads the controls and presses a button as a\nscreen reader does (`tests/access/windows.ps1`).\n\n| Test | What it checks | \n|---|---|\n| `test_widgets` | clicks, slide-off, default button, typing and the clipboard, Tab and Space, mnemonics, lists, modal dialogs, pixels, frame skipping, hover (mouse yes, finger no), Back versus Exit | \n| `test_business` | dates, number formats, sorting; the grid (selection, multi-select, sorting, editing, resizing, every cell kind, cell colours, record indicator, totals); tree; icon view; calendar and date picker; check list; number fields; auto-complete; splitter; dialogs | \n| `test_text` | fonts (FreeSans the default, its real bold, oblique and bold oblique, GPOS kerning, crisp stems at 100 %, 125 %, 150 % and 2x, Arabic joined in every face and in a font file, the settings of before), Arabic joining and lam-alef, bidi order, the rasterizer, sizes, rows, `spring` ,`adaptive` , forms, page scrolling both ways with the standard scroll bars, Tab scrolling into view, Markdown (right to left in Arabic: lists and tables) | \n| `test_bidi` | the bidi algorithm against Unicode's (a corpus of Arabic, Persian and mixed lines, explicit embeddings and isolates, conformance lines), the joiners, Arabic in text boxes and the rich editor (caret, marks, Backspace and Delete, selections, the clipboard), PDF read back in reading order, right-to-left Excel sheets, the Arabic interface's text boxes and charts | \n| `test_uifonts` | the font choices and their settings words, a .ttc collection read and used as the UI's font, the canvas's font names, every character of the kit's strings and the Arabic translations in the fonts | \n| `test_input` | every kind of input on the widgets that take it: the slide switch, the tab strip, chips, the bottom bar, the menu key, the mouse's side buttons, a tall dialog on a touch screen | \n| `test_files` | listings (quick and full), natural sorting, copy / move / delete / zip / unzip jobs with conflicts, links and cancelling, JPEG decoding, big text and hex files, worker threads, thousands of damaged files, zip bombs, names that climb out | \n| `test_apps` | settings rows, appearance, avatars, steps, pages, banners, the bottom bar, the folder model, the file list, thumbnails, the preview, text and hex views, properties, file jobs | \n| `test_explorer` | drag and drop (mouse, Ctrl, Esc, a finger held then moved), drops and the jobs and undo they lead to, the tab strip, the path bar (clicks on folders, the folder menus, typing, suggestions), names first and then sizes, balloons | \n| `test_docfs` | another app's folder through a fake documents provider: \"Allow access\", listing, reading and writing, renames, every file job in and out of it, undo | \n| `test_records` | masked fields (typing, pasting, Arabic digits), the record navigator by mouse and keys, date ranges, the dual list, the property grid, the tree grid, the wizard | \n| `test_desktop` | MDI windows (moving, sizing, min / max / restore, the control menu, z order, Ctrl+F6 and Ctrl+F4, cascade and tile, clicks blocked under windows), find and replace, hot keys, the About box | \n| `test_charts` | every kind of chart with odd data (empty, NaN, negative, huge), axes, legends, tooltips by mouse and keys, events, CSV reading, PNG export | \n| `test_planning` | the kanban board, Gantt, agenda and org chart by mouse, keys and finger; KPI tiles | \n| `test_mapview` | the built-in countries, lookups, values, markers, routes, zoom and pan, hits | \n| `test_windows` | windows besides the app's own: opened, drawn, their input, closed by their button or by not being drawn, a dialog where the system has one window | \n| `test_capture` | the camera and the microphone without either: a webcam's YUYV picture turned into pixels exactly, a recording's WAV header, the view and the recorder saying plainly there is nothing | \n| `test_mediasession` | the app's MPRIS player on a session bus of the test's own: its state and track as a desktop's applet reads them, the applet's buttons coming back in `mediaKeys` | \n| `test_view3d` | meshes, OBJ and STL (text and binary), the camera, picking, rendering | \n| `test_htmlview` | the HTML parser, entities, nesting, tables, pictures, links, `htmlToMarkdown` , the web view's fallbacks, its talk with the page (messages, scripts' answers, allowed hosts, zoom, Forward) through a fake system | \n| `test_media` | WAV decoding and encoding, tones, GIF frames, the video and animation widgets without a device | \n| `test_native` | loading libraries and functions, the cross-platform calls, neutral answers where there is no such thing | \n| `test_photo` | the image engine: blend modes, layers, undo and cancelled steps, the selection and channels, Quick Mask, paths, every painting tool, transforms, color modes, every filter and adjustment, .rpd / PNG / JPEG / BMP files | \n| `test_canvas2d` | the Canvas 2D API: paths, fills and strokes, transforms, gradients and patterns, text, pictures, clipping, compositing, against expected pixels | \n| `test_pdf` ,`test_report` | the PDF writer (structure, fonts, pictures, cross-reference table), templates, HTML reports, page breaks, repeated headers, totals, previews | \n| `test_apk` | the APK tool's SHA-256, CRC-32, deflate and PNG; the App Bundle's proto manifest and resources, its JAR signature (checked with `jarsigner` and`bundletool` when they are there) | \n| `test_store` | DER, certificates and their subjects, JAR signatures, Authenticode (the digest, the checksum, signing again, a time stamp from an authority made with OpenSSL), MSIX (ZIP64 records, the block map's blocks one by one, the signed digests rebuilt by the test), macOS: the `.icns` , the code directory read back page by page (one architecture and universal), the sealed resources, the`.ipa` ;`osslsigncode` and`rcodesign` when they are there | \n| `test_tilemap` | the street map: tiles of every kind of source, the cache, dragging, zooming, markers and shapes, the services' answers (places and routes of each), polylines, GCJ-02, GeoJSON, vector tiles decoded and drawn, PMTiles and MBTiles read and written | \n| `test_system` | `retroui/system` on Linux: what answers and what says false, the autostart entry made and taken away, the answers of a page and of phones read right, speech when a voice is installed | \n| `test_update` | Ed25519 against RFC 8032, signatures checked (a byte changed, another key), versions compared, an update found, checked and downloaded from a small server, a changed file or update file refused | \n| `test_fontsoption` | `-d:retroFonts` : the faces left out drawn with FreeSans, or read from a folder of their files | \n| `test_gltf` | glTF read and written, the car's parts moved, turned, hidden and tinted, its clips (linear, step, cubic), shapes and bones, a picture on a surface (unlit), damaged files refused, Blender's `.blend` when Blender is there | \n| `test_sysmon` ,`test_taskman` | what the computer is doing: a sample's numbers in their ranges, this program among the processes with its start time, the reading thread, a child given another priority and ended, an id that is another process's left alone, the autostart entries turned off and on, a socket of its own among the connections, the history on disk, the live graph's frame with a new value; Retro Task Manager as a program -- the list and the selection kept on the same process as samples come, search and sort, the graphs, the other pages, paused | \n| `test_board` ,`test_boardapp` | the whiteboard: ink, shapes, arrows held by shapes, typing, undo, files, PDF, the memory kept; Retro Board as a program -- its docked panels, Tab, the panels kept between runs, Zen and View mode and the button that leaves them | \n| `test_xmpp` | chat on XMPP with a small server of the test's own: signing on, the account's archive found, a page of it (yours and theirs, in order, with their archive ids), a forged answer from someone else left out; a port where nothing answers | \n| `test_omemo` | OMEMO: X25519, Ed25519 and HKDF against the RFCs' vectors, XEdDSA (the identity's X25519 key and its signatures, keys of both signs), AES-GCM, protobuf; two devices talking (a session from a bundle, answers, messages out of order, one twice, a changed one, one not for this device, the store read back from its file), trust (a verified device: only it), a bundle not signed right; files (aesgcm://) | \n| `test_access` | the screen readers' tree: every control's role, name (from the label before it), value, state, place in its list; the grid's headers, rows and cells, the tree's levels, the check list, the calendar's days, charts; presses, text, values and focus asked for, done by the next frame; nothing made without a screen reader | \n| `test_atspi` | Orca's side on a real X server and AT-SPI bus: the window's objects walked, text put in a box, a button pressed, the focus moved and its event heard | \n| `test_uia` | (Windows, also under Wine) the UI Automation provider: the tree a client reads, names, the patterns (invoke, value, toggle, range, selection) | \n| `test_scale` | fractional scales (125 % to 300 %): controls and text the size asked for, lines on whole pixels, a change of scale followed | \n| `test_touchsizes` | `RETRO_TOUCH` and`RETRO_CONTROLS` : finger-sized controls on a tablet's screen when asked, desktop and automatic sizes | \n| `test_shm` | the frame drawn into memory shared with the X server and what the window then shows, with and without it ( `RETRO_SURFACE=0` ) | \n| `test_designer` | Retro Designer: every example app read and drawn, each of their controls deleted and the file still Nim, every control of the Widget Box dropped into one app that compiles, moves, groups, events, properties, renames, undo, the lock on the file; `windowBox` ,`inert` ,`showMenu` | \n| `android_mock/run.sh` | the real Android driver against a fake Android (looper, input queue, window, JNI): frames, taps, keyboard, Arabic text, insets, cancelled touches, keys, rotation, Back, an input method composing | \n| `soak_maps3d` (not in`nimble test` : a few minutes,`nim c -r -d:release -d:useMalloc tests/soak_maps3d.nim` ) | a long session: the street map moved and zoomed over the world through thousands of tiles, then a model's clips played for thousands of frames -- the memory levels off | \n\n| File | What it is | \n|---|---|\n| `examples/hello.nim` | the first app above | \n| `examples/gallery.nim` | every control, page by page (also an APK: `nimble galleryapk` ) | \n| `examples/filemanager.nim` | Retro Files: an Explorer-class file manager (also an APK: `nimble apk` ) | \n| `examples/ledger.nim` | a small accounting app: grids, forms, CSV, reports | \n| `examples/notepad.nim` | a 3.1 Notepad: open, save, cut / copy / paste, time and date, find, print to PDF, \"Save changes?\" | \n| `examples/communicator.nim` | Retro Communicator: chat on XMPP -- friends, groups, rooms, away messages, profiles, files, the history synced from the server, voice messages, OMEMO encryption ( `-d:ssl` ) | \n| `examples/charts.nim` | Chart Viewer: open a CSV, pick a chart, save a PNG | \n| `examples/invoice.nim` | an invoice from an HTML template: preview, PDF, print | \n| `examples/native.nim` | calling the system's own APIs on each platform | \n| `examples/photo.nim` | Retro Photo 4: an image editor of 1994 laid out as today's image editors -- layers and groups, Smart Objects, 62 tools, filters, Neural Filters, PDF for print, Arabic type, panoramas, raw photos | \n| `examples/magazine.nim` | a three-page magazine made with Retro Photo's engine, tool by tool, saved as .rpd and PDF | \n| `examples/taskman.nim` | Retro Task Manager: the processes (end one or a tree, a priority), the processor, the memory, the disks and the network as graphs that move, what starts with the system, the connections, the last week (also an APK:  |", "url": "https://wpnews.pro/news/retro-nim-ui-windows-3-1-style-ui-toolkit", "canonical_source": "https://github.com/foxoman/retroui", "published_at": "2026-11-09 19:14:22+00:00", "updated_at": "2026-11-09 19:23:26.182075+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Sultan Al Isaiee", "retroui", "Nim", "retrokit", "Claude", "Anthropic", "Windows", "Android"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/retro-nim-ui-windows-3-1-style-ui-toolkit", "markdown": "https://wpnews.pro/news/retro-nim-ui-windows-3-1-style-ui-toolkit.md", "text": "https://wpnews.pro/news/retro-nim-ui-windows-3-1-style-ui-toolkit.txt", "jsonld": "https://wpnews.pro/news/retro-nim-ui-windows-3-1-style-ui-toolkit.jsonld"}}