module ui_cocoa
module lib/ui_cocoa.mu
import "ui_cocoa"
ui_cocoa is the macOS backend behind ui: widgets are real AppKit controls reached through objc, and the event loop pumps NSApplication while draining the action queue Cocoa fills. Programs import ui, never this module; ui hands every call here on darwin.
The shared state map u is owned by ui. This backend keeps its own keys in it: "app" (NSApplication), "queue" (the NSMutableArray every control's target/action appends the sender to), "menued", plus the shared "windows", "widgets" (sender pointer → widget handle) and "handlers" (sender pointer → closure) it dispatches with.
Imports
Functions
backend#
backend answers the function table ui dispatches through.
Source lib/ui_cocoa.mu:16
fn backend() {
return {"init": init, "window": window, "label": label, "button": button, "input": input, "checkbox": checkbox, "textarea": textarea, "set_text": set_text, "get_text": get_text, "get_checked": get_checked, "set_checked": set_checked, "place": place, "remove": remove, "run": run}
}button#
button places a push button whose click lands in onclick(widget).
Source lib/ui_cocoa.mu:97
fn button(u, win, title, x, y, w, h, onclick) {
// Cocoa itself queues the sender: the target is our NSMutableArray and
// the action is addObject:, so a click appends the button to the queue
// and run() maps it back to onclick.
btn := objc.send(objc.cls("NSButton"), "buttonWithTitle:target:action:", objc.nsstring(title), u["queue"], objc.sel("addObject:"))
if is_error(btn) {
return btn
}
handle := _place_view(u, win, btn, "button", x, y, w, h)
if is_error(handle) {
return handle
}
handle["onclick"] = onclick
return handle
}checkbox#
checkbox places a toggle whose change lands in onchange(widget).
Source lib/ui_cocoa.mu:142
fn checkbox(u, win, title, x, y, w, h, onchange) {
cb := objc.send(objc.cls("NSButton"), "checkboxWithTitle:target:action:", objc.nsstring(title), u["queue"], objc.sel("addObject:"))
if is_error(cb) {
return cb
}
handle := _place_view(u, win, cb, "checkbox", x, y, w, h)
if is_error(handle) {
return handle
}
handle["onchange"] = onchange
return handle
}get_checked#
get_checked answers a checkbox's state.
Source lib/ui_cocoa.mu:213
fn get_checked(_, widget) {
return objc.send(widget["ptr"], "state") & 255 == 1
}get_text#
get_text answers a widget's current text.
Source lib/ui_cocoa.mu:197
fn get_text(_, widget) {
kind := widget["kind"]
if kind == "button" || kind == "checkbox" || kind == "window" {
return objc.utf8(objc.send(widget["ptr"], "title"))
}
if kind == "textarea" {
return objc.utf8(objc.send(widget["ptr"], "string"))
}
return objc.utf8(objc.send(widget["ptr"], "stringValue"))
}init#
init brings up the application object and the action queue.
Source lib/ui_cocoa.mu:22
fn init(u) {
if u["app"] != nil {
return true
}
ok := objc.init()
if is_error(ok) {
return ok
}
app := objc.send(objc.cls("NSApplication"), "sharedApplication")
if is_error(app) {
return app
}
// NSApplicationActivationPolicyRegular: a real app with a Dock icon that
// can come to the foreground.
objc.send(app, "setActivationPolicy:", 0)
queue := objc.send(objc.send(objc.cls("NSMutableArray"), "alloc"), "init")
if is_error(queue) {
return queue
}
u["app"] = app
u["queue"] = queue
return true
}input#
input places an editable text field; Return lands in onsubmit(widget).
Source lib/ui_cocoa.mu:119
fn input(u, win, text, x, y, w, h, onsubmit) {
fld := objc.send(objc.cls("NSTextField"), "textFieldWithString:", objc.nsstring(text))
if is_error(fld) {
return fld
}
handle := _place_view(u, win, fld, "input", x, y, w, h)
if is_error(handle) {
return handle
}
// Wire the action unconditionally so a handler assigned to the handle
// after creation still fires, the way the X11 backend reads it live.
objc.send(fld, "setTarget:", u["queue"])
objc.send(fld, "setAction:", objc.sel("addObject:"))
handle["onsubmit"] = onsubmit
return handle
}label#
label places a wrapping static text label.
Source lib/ui_cocoa.mu:85
fn label(u, win, text, x, y, w, h) {
lbl := objc.send(objc.cls("NSTextField"), "wrappingLabelWithString:", objc.nsstring(text))
if is_error(lbl) {
return lbl
}
return _place_view(u, win, lbl, "label", x, y, w, h)
}place#
place moves and resizes a view (top-left coordinates, flipped through the window's CURRENT content height, so it stays right after a resize).
Source lib/ui_cocoa.mu:232
fn place(_, widget, x, y, w, h) {
win := widget["win"]
flipped := _content_h(win) - y - h
r := objc.invoke(widget["ptr"], "setFrame:", [objc.rect(x, flipped, w, h)])
if is_error(r) {
return r
}
return nil
}remove#
remove takes a view out of its window and forgets its handler.
Source lib/ui_cocoa.mu:246
fn remove(u, widget) {
objc.send(widget["ptr"], "removeFromSuperview")
key := str(widget["ptr"])
if has(u["handlers"], key) {
del(u["handlers"], key)
}
if has(u["widgets"], key) {
del(u["widgets"], key)
}
return nil
}run#
run pumps the Cocoa event loop until u["running"] goes false or the last window closes.
Source lib/ui_cocoa.mu:264
fn run(u) {
app := u["app"]
_ensure_menu(u)
objc.send(app, "activateIgnoringOtherApps:", true)
objc.send(app, "finishLaunching")
mode := objc.nsstring("kCFRunLoopDefaultMode")
while u["running"] {
pool := objc.pool()
_show_new_windows(u)
// Fire due timers; the budget until the next one bounds the wait.
tick := u["tick"]
budget := tick()
// Block until an event or the next timer: idle costs nothing.
date := _wait_date(budget)
e := objc.send(app, "nextEventMatchingMask:untilDate:inMode:dequeue:", 0 - 1, date, mode, 1)
if !is_error(e) && e != 0 {
objc.send(app, "sendEvent:", e)
// Drain whatever queued behind it without blocking again.
n := 0
while n < 100 {
e = objc.send(app, "nextEventMatchingMask:untilDate:inMode:dequeue:", 0 - 1, 0, mode, 1)
if is_error(e) || e == 0 {
break
}
objc.send(app, "sendEvent:", e)
n = n + 1
}
}
_dispatch(u)
_poll_closed(u)
if !_any_window_open(u) {
u["running"] = false
}
objc.drain(pool)
}
return nil
}set_checked#
set_checked flips a checkbox programmatically.
Source lib/ui_cocoa.mu:219
fn set_checked(_, widget, v) {
state := 0
if v {
state = 1
}
return objc.send(widget["ptr"], "setState:", state)
}set_text#
set_text replaces a widget's text.
Source lib/ui_cocoa.mu:180
fn set_text(_, widget, text) {
ns := objc.nsstring(text)
kind := widget["kind"]
if kind == "button" || kind == "checkbox" || kind == "window" {
return objc.send(widget["ptr"], "setTitle:", ns)
}
if kind == "textarea" {
return objc.send(widget["ptr"], "setString:", ns)
}
return objc.send(widget["ptr"], "setStringValue:", ns)
}textarea#
textarea places a multi-line editable text view (fixed size, no scrolling).
Source lib/ui_cocoa.mu:161
fn textarea(u, win, text, x, y, w, h) {
tv := objc.send(objc.send(objc.cls("NSTextView"), "alloc"), "init")
if is_error(tv) {
return tv
}
handle := _place_view(u, win, tv, "textarea", x, y, w, h)
if is_error(handle) {
return handle
}
objc.send(tv, "setString:", objc.nsstring(text))
return handle
}window#
window creates a titled, closable, resizable window of the given content size, hidden until run() shows it.
Source lib/ui_cocoa.mu:56
fn window(_, title, w, h) {
win := objc.send(objc.cls("NSWindow"), "alloc")
if is_error(win) {
return win
}
// styleMask 15 = titled | closable | miniaturizable | resizable;
// backing 2 = buffered. The rect goes through NSInvocation: it is a
// float aggregate the plain FFI cannot pass.
inv := objc.invoke(win, "initWithContentRect:styleMask:backing:defer:", [objc.rect(0, 0, w, h), _pack_int(15), _pack_int(2), _pack_int(0)])
if is_error(inv) {
return inv
}
out := buf(8)
objc.send(inv, "getReturnValue:", out)
win = _unpack_int(out)
objc.send(win, "setTitle:", objc.nsstring(title))
objc.send(win, "center")
// Closing must hide the window, never deallocate it: the µ handle keeps
// pointing at it, and run() polls it to notice the close.
objc.send(win, "setReleasedWhenClosed:", false)
return {"kind": "window", "ptr": win, "w": w, "h": h, "_shown": false, "_closed": false, "onclose": nil}
}Internal helpers
Underscore-prefixed names are implementation detail. They are listed so the module's source reads without surprises, not as API — they may change at any time.
| _any_window_open(u) | — |
| _content_h(win) | — |
| _dispatch(u) | — |
| _ensure_menu(u) | — |
| _pack_int(v) | — |
| _place_view(u, win, view, kind, x, y, w, h) | — |
| _poll_closed(u) | — |
| _show_new_windows(u) | — |
| _unpack_int(b) | — |
| _wait_date(budget) | — |
| _window_open(p) | — |