Status: IMPURE TOOL — not part of the dual-compiler purity guarantee.
The quarantined desktop-window backend for the pure
sml-ui widget toolkit. This is the
one place in the pure-SML GUI stack that touches the outside world: it opens a
real OS window via a tiny minifb FFI shim,
blits sml-ui's RGBA framebuffer into it every frame, and feeds real
mouse/keyboard input back into sml-ui's pure input model — so you can actually
click the checkboxes, pick the radio, drag the sliders, type in the text
field, open the dropdown, switch tabs, and open/close the modal.
It mirrors the established sjqtentacles convention for impure edges
(sml-readline's pure core / future driver split, sml-serve as the web
stack's socket edge, sml-httpc-tool as the HTTP client's socket driver):
the portable, tested, deterministic artifact is the pure toolkit; the on-screen
tool is a thin, compiler-specific, unguaranteed driver.
Both images are the actual framebuffer minifb displayed on screen, captured
by ./bin/demo --shot (left: the GENERAL tab with the QUALITY dropdown open;
right: HELP ▸ ABOUT opening the modal over a dimmed backdrop). The pixels come
straight from the live sml-ui toolkit — sml-window only pumps them into the
window.
- Non-deterministic & compiler-specific. The window's contents depend on
what you do with the mouse and keyboard, and the FFI uses MLton's
_import/_addressC interface. It is therefore MLton-only and not part of the byte-identical dual-compiler test suite. - Targets MLton on macOS (darwin). The shim is built and verified against MLton 20210117 on macOS/arm64, linking minifb's Cocoa/Metal backend. minifb itself is cross-platform (Windows, X11, Wayland), so porting the link flags is the only work needed elsewhere — but this repo only claims macOS.
- The pure logic is still dual-compiler tested. The two FFI-free halves —
WinInput(raw OS poll →Ui.frameinput) andSettings(the demo's model:view+update) — are ordinary total functions and pass byte-identically on both MLton and Poly/ML (40 deterministic checks). CI runs those on both compilers and additionally type-checks the impure shim under MLton; it never opens a window.
minifb is not vendored into the repo; instead make minifb fetches it and
builds it into a static archive that MLton links against. This keeps the C
backend (which is platform Objective-C on macOS) out of the SML source tree
while making the build fully reproducible:
make minifb # git clone emoon/minifb into third_party/, cmake-build libminifb.aThe C shim c/smlwin.c (a ~150-line wrapper: open / blit RGBA8 / poll
mouse+keyboard / close) is compiled and linked by MLton together with the SML:
mlton -default-ann 'allowFFI true' \
-cc-opt '-Ithird_party/minifb/include' \
-link-opt 'third_party/minifb/build/libminifb.a \
-framework Cocoa -framework Metal -framework MetalKit \
-framework QuartzCore -lc++' \
-output bin/demo examples/sources.mlb c/smlwin.c(-lc++ is needed because minifb's static archive contains a C++ translation
unit and MLton links with cc, not c++.)
make minifb # one-time: fetch + build the minifb static library
make demo # build bin/demo (MLton + the FFI shim)
make run # build + open the interactive window (macOS, NEEDS A DISPLAY)
make test # the PURE test suite under MLton (deterministic, no window)
make test-poly # the PURE test suite under Poly/ML (byte-identical)
make all-tests # both — must agree
./bin/demo --shot assets/window.png # drive the live UI on screen + save the frame OS events pixels on screen
│ ▲
▼ │
Window.poll ──► WinInput.step ──► Ui.render ──► Settings.update ──► Window.update
(impure FFI) (PURE) (PURE, sml-ui) (PURE) (impure FFI/blit)
WinInput(pure, tested). Turns a raw, OS-agnostic poll snapshot (mouse x/y + button, typed unicode code points, special keys) into the pureUi.frameinput: button press/release edges becomeMouse(Down)/Mouse(Up)(the toolkit's "click"), pointer motion becomesMouse(Move), code points become UTF-8Chars, and Tab becomesFocusNext.Settings(pure, tested). The demo's whole application model:viewbuilds thesml-uiwidget tree from the model, andupdatefolds the toolkit's fired events (toggle,select,change,tab,menu,open,close,input,click) back into it.Window(impure, MLton_import). The thin minifb shim:open_,update(blit anImage.image+ pump events),poll(→WinInput.raw),waitSync,close.
Because the entire input→model→view path is pure, the interesting behaviour is
unit-tested headlessly (including an integration test that routes a
WinInput-produced click through the live sml-ui engine). The window driver
itself is smoke-tested by actually opening the window on macOS — the screenshots
above are that run.
Layout B. Vendors sml-ui and its entire transitive pure dependency diamond
(sml-color, sml-inflate, sml-image, sml-raster, sml-pretty, sml-svg,
sml-canvas2d, sml-font, sml-unicode, sml-text-layout, sml-layout,
sml-signal) verbatim under lib/, loaded as one flat, single-copy .mlb
(src/deps.mlb) so the shared Image.image type elaborates exactly once. The
impure edge is just c/smlwin.c + src/window.sml.
MIT — see LICENSE.

