moon_ui

A declarative widget toolkit in the spirit of vlang's ui.

moon_ui.event

const EV_NONE#

Event code indicating that no event is available.

const EV_EXPOSE#

Event code requesting a redraw.

const EV_MOUSE_DOWN#

Event code for a mouse-button press.

const EV_MOUSE_UP#

Event code for a mouse-button release.

const EV_MOUSE_MOVE#

Event code for mouse-pointer motion.

const EV_KEY#

Event code for keyboard input.

const EV_RESIZE#

Event code for a window resize.

const EV_CLOSE#

Event code requesting window closure.

const KEY_BACKSPACE#

Backend key code for Backspace.

const KEY_TAB#

Backend key code for Tab.

const KEY_RETURN#

Backend key code for Return.

const KEY_ESCAPE#

Backend key code for Escape.

const KEY_HOME#

Backend key code for Home.

const KEY_LEFT#

Backend key code for the left arrow.

const KEY_RIGHT#

Backend key code for the right arrow.

const KEY_END#

Backend key code for End.

const KEY_DELETE#

Backend key code for Delete.

struct UiEvent#

One backend-independent input event.

kind: int

Which event this is, as one of the EV_* constants.

x: int

Pointer column in pixels, for a mouse event.

y: int

Pointer row in pixels, for a mouse event.

button: int

Button index, for a press or release.

key: int

Key code, for a key event.

ch: string

Typed text, for a text-input event.

width: int

New window width, for a resize.

height: int

New window height, for a resize.

fn no_event() -> UiEvent#

A no-op event (kind EV_NONE).

moon_ui.font

const WIDTH#

Bitmap glyph width in pixels.

const HEIGHT#

Bitmap glyph height in pixels.

const ASCENT#

Pixels above the bitmap font baseline.

const DESCENT#

Pixels below the bitmap font baseline.

const FIRST#

First supported ASCII code point.

const LAST#

Last supported ASCII code point.

fn rows() -> [int]#

Flat glyph rows: (LAST - FIRST + 1) glyphs x HEIGHT ints; in each row int, bit x (1 << x) set means the pixel x columns from the left is on. Call once and keep the array; it allocates per call.

moon_ui.sdl

struct Backend#

An open SDL window/renderer plus the glyph bitmaps text draws from. Value-copied freely; open/resize return updated values.

window: *void

The SDL window handle.

renderer: *void

The SDL renderer drawing goes through.

glyphs: [int]

Bitmap rows of the built-in font, as font.rows() returns them.

ascent: int

Pixels from the baseline to the top of the font.

descent: int

Pixels from the baseline to the bottom of the font.

width: int

Current window width in pixels.

height: int

Current window height in pixels.

fn open(title: string, width: int, height: int, bg: int) -> Optional<Backend>#

Opens a width x height window titled title. bg (0xRRGGBB) is the first clear color. Returns none when SDL cannot open a display.

  • title: Window title.
  • width: Window width in pixels.
  • height: Window height in pixels.
  • bg: First clear colour, as 0xRRGGBB.
use moon_ui.sdl

fn main() {
  match sdl.open("moon", 320, 180, 0x101828) {
    some(backend) => {
      sdl.fill_rect(backend, 10, 10, 100, 40, 0x7DD3FC)
      sdl.stroke_rect(backend, 10, 10, 100, 40, 0xFFFFFF)
      sdl.line(backend, 10, 70, 200, 70, 0xFFFFFF)
      sdl.fill_circle(backend, 30, 90, 20, 0x7DD3FC)
      sdl.text(backend, 60, 90, "moon", 0xFFFFFF)
      println(sdl.text_width(backend, "moon"))
      println(sdl.line_height(backend))
      sdl.present(backend)
      if sdl.pending(backend) { println(sdl.next_event(backend)) }
      sdl.sync(backend)
      sdl.close(backend)
    },
    none => println("no display"),
  }
}
fn resize(b: Backend, width: int, height: int) -> Backend#

Records the new window size (the SDL renderer tracks it on its own).

  • b: The open window this draws on.
  • width: New window width in pixels.
  • height: New window height in pixels.
fn fill_rect(b: Backend, x: int, y: int, w: int, h: int, color: int)#

Fills a rectangle on the drawing target.

  • b: The open window this draws on.
  • x: Left edge in pixels.
  • y: Top edge in pixels.
  • w: Width in pixels.
  • h: Height in pixels.
  • color: Colour as 0xRRGGBB.
fn stroke_rect(b: Backend, x: int, y: int, w: int, h: int, color: int)#

1px outline of the rectangle.

  • b: The open window this draws on.
  • x: Left edge in pixels.
  • y: Top edge in pixels.
  • w: Width in pixels.
  • h: Height in pixels.
  • color: Colour as 0xRRGGBB.
fn line(b: Backend, x1: int, y1: int, x2: int, y2: int, color: int)#

Draws a line on the target.

  • b: The open window this draws on.
  • x1: Start column in pixels.
  • y1: Start row in pixels.
  • x2: End column in pixels.
  • y2: End row in pixels.
  • color: Colour as 0xRRGGBB.
fn fill_circle(b: Backend, x: int, y: int, d: int, color: int)#

Filled circle inside the (x, y, d, d) square, rasterized in moon as one horizontal span per row. Odd diameters center exactly; even diameters use the upper-left of the two candidate centers.

  • b: The open window this draws on.
  • x: Left edge of the bounding square.
  • y: Top edge of the bounding square.
  • d: Diameter in pixels.
  • color: Colour as 0xRRGGBB.
fn stroke_circle(b: Backend, x: int, y: int, d: int, color: int)#

Circle outline inside the (x, y, d, d) square: span endpoints per row, plus the transposed pass so shallow arcs have no gaps.

  • b: The open window this draws on.
  • x: Left edge of the bounding square.
  • y: Top edge of the bounding square.
  • d: Diameter in pixels.
  • color: Colour as 0xRRGGBB.
fn text_width(_b: Backend, text: string) -> int#

Pixel width of text in the built-in font.

  • _b: The open window, unused: the built-in font is fixed.
  • text: Text to measure.
fn line_height(b: Backend) -> int#

Line height (ascent + descent) of the built-in font.

  • b: The open window this draws on.
fn text(b: Backend, x: int, y: int, s: string, color: int)#

Draws s with its baseline at y (use b.ascent to convert from a top coordinate). Glyph rows are drawn as horizontal runs of set bits; characters outside the font's ASCII range render as blanks.

  • b: The open window this draws on.
  • x: Left edge of the first glyph, in pixels.
  • y: Baseline row in pixels.
  • s: Text to draw.
  • color: Colour as 0xRRGGBB.
fn present(b: Backend)#

Flips the finished frame to the window.

  • b: The open window this draws on.
fn pending(_b: Backend) -> bool#

True when an event is already queued (next_event will not block). SDL_PollEvent with a null pointer peeks without dequeuing.

  • _b: The open window, unused: the SDL queue is global.
fn next_event(_b: Backend) -> UiEvent#

Blocks for the next SDL event and translates it. Events the UI does not model translate to EV_NONE and should be ignored.

  • _b: The open window, unused: the SDL queue is global.
fn sync(_b: Backend)#

Makes queued OS events visible to pending (used by tests).

  • _b: The open window, unused: the SDL queue is global.
fn close(b: Backend)#

Closes the resource.

  • b: The window and renderer to release.

moon_ui.ui

const UNSET#

Sentinel for color parameters: "use the theme default".

enum Kind#

Widget kinds carried by both specs and arena nodes.

Column

A container stacking its children vertically.

Row

A container laying its children out horizontally.

Label

Static text.

Button

A clickable button.

Checkbox

A box that toggles between ticked and clear.

TextBox

A single-line editable text field.

Slider

A draggable value along a track.

Progress

A read-only fill showing progress.

Rectangle

A plain filled rectangle.

Radio

A stack of mutually exclusive options.

Canvas

A free-form region painted by a callback.

enum Align#

Cross-axis alignment of a container's children.

Start

Flush with the leading edge.

Center

Centred on the cross axis.

End

Flush with the trailing edge.

struct Metrics#

Font metrics the layout runs against. run installs the backend font; tests install a fake (fixed advance per character).

measure: fn(string) -> int

Pixel width of a string in the installed font.

ascent: int

Pixels from the baseline to the top of the font.

descent: int

Pixels from the baseline to the bottom of the font.

fn fake_metrics(char_w: int, ascent: int, descent: int) -> Metrics#

Fixed-advance metrics for headless layout tests.

  • char_w: Pixels every character advances.
  • ascent: Pixels from the baseline to the top of the font.
  • descent: Pixels from the baseline to the bottom of the font.
struct Shared#

Mutable per-app bookkeeping owned directly by the managed App.

hover: int

Arena index under the pointer, or -1.

pressed: int

Arena index the pointer went down on, or -1.

focus: int

Arena index holding keyboard focus, or -1.

drag: int

Arena index of the slider being dragged, or -1.

dirty: bool

Whether the frame needs repainting.

quit: bool

Whether the loop should end after this event.

win_w: int

Current window width in pixels.

win_h: int

Current window height in pixels.

bg: int

Window clear colour as 0xRRGGBB.

struct Node#

One arena widget: the spec fields plus computed geometry (x/y/w/h) and runtime editing state (cursor/scroll for a TextBox).

kind: Kind

Which widget this is.

id: string

Name callbacks address the widget by; empty is unaddressable.

parent: int

Arena index of the enclosing container, or -1 for the root.

children: [int]

Arena indices of the children, in paint order.

text: string

Label, button caption, or text-field contents.

options: [string]

Choice labels, for a radio group.

selected: int

Index of the chosen option, for a radio group.

checked: bool

Whether the box is ticked, for a checkbox.

value: float

Current value, for a slider or progress bar.

min: float

Lowest value the slider or progress bar represents.

max: float

Highest value the slider or progress bar represents.

width: int

Requested width in pixels; 0 lets the layout decide.

height: int

Requested height in pixels; 0 lets the layout decide.

padding: int

Pixels of inset between a container and its children.

spacing: int

Pixels between adjacent children of a container.

color: int

Foreground colour as 0xRRGGBB, or UNSET to inherit.

bg: int

Background colour as 0xRRGGBB, or UNSET for none.

align: Align

How children sit on the cross axis of a container.

on_click: fn(Ctx) -> void

Called with a context handle when the widget is clicked.

on_change: fn(Ctx) -> void

Called with a context handle when the value changes.

draw: fn(Painter) -> void

Called each frame with a canvas-relative painter, for a canvas.

x: int

Computed left edge in window pixels.

y: int

Computed top edge in window pixels.

w: int

Computed width in pixels.

h: int

Computed height in pixels.

cursor: int

Caret byte offset, for a text field.

scroll: int

Horizontal scroll offset in pixels, for a text field.

struct App#

A running UI. build allocates one managed instance, while the node arena and id table keep their ordinary collection identity.

nodes: [Node]

The widget arena, in paint order.

ids: {string: int}

Widget ids to their arena indices.

state: Shared

Hover, focus, drag, and window bookkeeping.

metrics: Metrics

The font metrics layout measures against.

struct Ctx#

The handle widget callbacks receive; query and mutate widgets by id.

app: *App

The running application the callback may query and mutate.

struct Painter#

Drawing surface handed to a canvas widget's draw callback; coordinates are relative to the canvas origin.

b: Backend

The backend the drawing lands on.

x: int

Left edge of the canvas in window pixels.

y: int

Top edge of the canvas in window pixels.

w: int

Canvas width in pixels.

h: int

Canvas height in pixels.

struct Widget#

A widget spec node; construct via the functions below, not literally.

kind: Kind

Which widget this is.

id: string

Name callbacks address the widget by; empty is unaddressable.

text: string

Label, button caption, or text-field contents.

options: [string]

Choice labels, for a radio group.

selected: int

Index of the chosen option, for a radio group.

checked: bool

Whether the box is ticked, for a checkbox.

value: float

Current value, for a slider or progress bar.

min: float

Lowest value the slider or progress bar represents.

max: float

Highest value the slider or progress bar represents.

width: int

Requested width in pixels; 0 lets the layout decide.

height: int

Requested height in pixels; 0 lets the layout decide.

padding: int

Pixels of inset between a container and its children.

spacing: int

Pixels between adjacent children of a container.

color: int

Foreground colour as 0xRRGGBB, or UNSET to inherit.

bg: int

Background colour as 0xRRGGBB, or UNSET for none.

align: Align

How children sit on the cross axis of a container.

on_click: fn(Ctx) -> void

Called with a context handle when the widget is clicked.

on_change: fn(Ctx) -> void

Called with a context handle when the value changes.

draw: fn(Painter) -> void

Called each frame with a canvas-relative painter, for a canvas.

children: [Widget]

Child specs, for a container.

struct Window#

A top-level window spec.

title: string

Window title.

width: int

Window width in pixels.

height: int

Window height in pixels.

bg: int

Clear colour as 0xRRGGBB.

children: [Widget]

Widgets of the root container.

fn window(title: string, width: int, height: int, bg: int, children: [Widget]) -> Window#

Creates a window widget and its root child container.

  • title: Window title.
  • width: Window width in pixels.
  • height: Window height in pixels.
  • bg: Clear colour as 0xRRGGBB.
  • children: Widgets of the root container.
use moon_ui.ui

fn increment(ctx: ui.Ctx) {
  ctx.set_text("count", to_string(std.strings.parse_int(ctx.text("count")) + 1))
  ctx.set_checked("enabled", true)
  ctx.set_value("progress", 75.0)
  ctx.set_selected("mode", 1)
}

fn draw_chart(painter: ui.Painter) {
  painter.fill_rect(0, 0, painter.width(), painter.height(), 0x101828)
  painter.line(8, painter.height() - 8, painter.width() - 8, 8, 0x7DD3FC)
  painter.circle(16, 16, 10, 0xFFFFFF)
  painter.text(28, 10, "moon", 0xFFFFFF)
}

fn main() {
  ui.run(ui.window(
    title: "moon",
    width: 420,
    height: 300,
    children: [ui.column(padding: 16, spacing: 10, children: [
      ui.label("0", id: "count"),
      ui.row(spacing: 8, children: [
        ui.button("Increment", on_click: increment),
        ui.checkbox("Enabled", id: "enabled"),
      ]),
      ui.textbox(id: "name", placeholder: "Project name"),
      ui.slider(id: "volume", value: 25.0),
      ui.progress(id: "progress", value: 25.0),
      ui.radio(["Debug", "Release"], id: "mode"),
      ui.rectangle(width: 40, height: 8, color: 0x7DD3FC),
      ui.canvas(draw: draw_chart, width: 180, height: 80),
    ])],
  ))
}
fn column(children: [Widget], spacing: int, padding: int, align: Align, width: int, height: int, bg: int) -> Widget#

Vertical container. align positions children on the cross (x) axis.

  • children: Widgets stacked top to bottom.
  • spacing: Pixels between adjacent children.
  • padding: Pixels of inset around the whole container.
  • align: How children sit on the x axis.
  • width: Requested width in pixels; 0 lets the layout decide.
  • height: Requested height in pixels; 0 lets the layout decide.
  • bg: Background colour as 0xRRGGBB, or UNSET for none.
fn row(children: [Widget], spacing: int, padding: int, align: Align, width: int, height: int, bg: int) -> Widget#

Horizontal container. align positions children on the cross (y) axis.

  • children: Widgets laid out left to right.
  • spacing: Pixels between adjacent children.
  • padding: Pixels of inset around the whole container.
  • align: How children sit on the y axis.
  • width: Requested width in pixels; 0 lets the layout decide.
  • height: Requested height in pixels; 0 lets the layout decide.
  • bg: Background colour as 0xRRGGBB, or UNSET for none.
fn label(text: string, id: string, color: int) -> Widget#

Creates a text label widget.

  • text: Text to display.
  • id: Name callbacks address the widget by.
  • color: Colour as 0xRRGGBB, or UNSET to inherit.
fn button(text: string, id: string, on_click: fn(Ctx) -> void, width: int, height: int) -> Widget#

Creates a button widget.

  • text: Caption to display.
  • id: Name callbacks address the widget by.
  • on_click: Called with a context handle when the button is clicked.
  • width: Requested width in pixels; 0 fits the caption.
  • height: Requested height in pixels; 0 fits the caption.
fn checkbox(text: string, id: string, checked: bool, on_change: fn(Ctx) -> void) -> Widget#

Creates a checkbox widget.

  • text: Label beside the box.
  • id: Name callbacks address the widget by.
  • checked: Whether the box starts ticked.
  • on_change: Called with a context handle when the value changes.
fn textbox(id: string, text: string, placeholder: string, width: int, on_change: fn(Ctx) -> void) -> Widget#

Single-line editable text field. text under the cursor keys, backspace, delete, home/end; fires on_change after every edit.

  • id: Name callbacks address the widget by.
  • text: Initial contents.
  • placeholder: Text shown while the field is empty.
  • width: Field width in pixels.
  • on_change: Called with a context handle after every edit.
fn slider(id: string, value: float, min: float, max: float, width: int, on_change: fn(Ctx) -> void) -> Widget#

Creates a slider widget.

  • id: Name callbacks address the widget by.
  • value: Initial value.
  • min: Lowest value the track represents.
  • max: Highest value the track represents.
  • width: Track width in pixels.
  • on_change: Called with a context handle as the handle moves.
fn progress(id: string, value: float, min: float, max: float, width: int, height: int) -> Widget#

Creates a progress widget.

  • id: Name callbacks address the widget by.
  • value: Initial value.
  • min: Value the bar reads as empty.
  • max: Value the bar reads as full.
  • width: Bar width in pixels.
  • height: Bar height in pixels.
fn rectangle(width: int, height: int, color: int) -> Widget#

Creates a rectangle widget.

  • width: Width in pixels.
  • height: Height in pixels.
  • color: Colour as 0xRRGGBB, or UNSET to inherit.
fn radio(options: [string], id: string, selected: int, on_change: fn(Ctx) -> void) -> Widget#

A stack of mutually exclusive options; selected is the chosen index.

  • options: Labels of the mutually exclusive choices.
  • id: Name callbacks address the widget by.
  • selected: Index of the chosen option.
  • on_change: Called with a context handle when the choice changes.
fn canvas(draw: fn(Painter) -> void, id: string, width: int, height: int) -> Widget#

Free-form drawing region; draw runs each frame with a Painter whose coordinates are canvas-relative.

  • draw: Called each frame with a canvas-relative painter.
  • id: Name callbacks address the widget by.
  • width: Canvas width in pixels.
  • height: Canvas height in pixels.
fn build(win: Window) -> *App#

Flattens a window spec into one managed App arena. run does this for you; tests call it directly and pair it with fake_metrics.

  • win: The window spec to flatten into an arena.
use moon_ui.event
use moon_ui.event.UiEvent
use moon_ui.ui

fn main() {
  let app = ui.build(ui.window(
    width: 320,
    height: 180,
    children: [ui.label("Ready", id: "status")],
  ))
  app.metrics = ui.fake_metrics(6, 11, 2)
  ui.layout(app)
  ui.dispatch(app, UiEvent {
    kind: event.EV_RESIZE,
    x: 0,
    y: 0,
    button: 0,
    key: 0,
    ch: "",
    width: 640,
    height: 360,
  })
  ui.dispatch(app, event.no_event())
  println(app.nodes.len())
}
impl Ctx#
fn text(self: Ctx, id: string) -> string#

The text of the widget with this id ("" when the id is unknown).

  • id: Identifier of the widget to address.
fn set_text(self: Ctx, id: string, text: string)#

Replaces the text of the named widget.

  • id: Identifier of the widget to address.
  • text: New contents.
fn checked(self: Ctx, id: string) -> bool#

Reports whether the named checkbox is selected.

  • id: Identifier of the widget to address.
fn set_checked(self: Ctx, id: string, checked: bool)#

Sets the checked state of the named widget.

  • id: Identifier of the widget to address.
  • checked: New checked state.
fn value(self: Ctx, id: string) -> float#

Current value of a slider or progress bar.

  • id: Identifier of the widget to address.
fn set_value(self: Ctx, id: string, value: float)#

Sets the numeric value of the named widget.

  • id: Identifier of the widget to address.
  • value: New value, clamped to the widget range.
fn selected(self: Ctx, id: string) -> int#

Selected option index of a radio group.

  • id: Identifier of the widget to address.
fn set_selected(self: Ctx, id: string, selected: int)#

Sets the selected index of the named widget.

  • id: Identifier of the widget to address.
  • selected: Index of the option to choose.
fn quit(self: Ctx)#

Ends the run loop after the current event.

fn layout(app: *App)#

Computes every node's geometry for the current metrics and window size.

  • app: The application whose node geometry is recomputed.
fn dispatch(app: *App, ev: UiEvent)#

Feeds one input event through the interaction model: hover/press/focus tracking, click and change callbacks, text editing, slider drags.

  • app: The application to feed the event to.
  • ev: One input event from the backend.
impl Painter#
fn width(self: Painter) -> int#

Returns the drawing-target width.

fn height(self: Painter) -> int#

Returns the drawing-target height.

fn fill_rect(self: Painter, x: int, y: int, w: int, h: int, color: int)#

Fills a rectangle on the drawing target.

  • x: Left edge, relative to the canvas.
  • y: Top edge, relative to the canvas.
  • w: Width in pixels.
  • h: Height in pixels.
  • color: Colour as 0xRRGGBB.
fn stroke_rect(self: Painter, x: int, y: int, w: int, h: int, color: int)#

Strokes a rectangle on the drawing target.

  • x: Left edge, relative to the canvas.
  • y: Top edge, relative to the canvas.
  • w: Width in pixels.
  • h: Height in pixels.
  • color: Colour as 0xRRGGBB.
fn line(self: Painter, x1: int, y1: int, x2: int, y2: int, color: int)#

Draws a line on the target.

  • x1: Start column, relative to the canvas.
  • y1: Start row, relative to the canvas.
  • x2: End column, relative to the canvas.
  • y2: End row, relative to the canvas.
  • color: Colour as 0xRRGGBB.
fn circle(self: Painter, x: int, y: int, d: int, color: int)#

Strokes a circle on the drawing target.

  • x: Left edge of the bounding square, relative to the canvas.
  • y: Top edge of the bounding square, relative to the canvas.
  • d: Diameter in pixels.
  • color: Colour as 0xRRGGBB.
fn fill_circle(self: Painter, x: int, y: int, d: int, color: int)#

Fills a circle on the drawing target.

  • x: Left edge of the bounding square, relative to the canvas.
  • y: Top edge of the bounding square, relative to the canvas.
  • d: Diameter in pixels.
  • color: Colour as 0xRRGGBB.
fn text(self: Painter, x: int, y: int, s: string, color: int)#

Draws s with its top-left corner at (x, y).

  • x: Left edge of the text, relative to the canvas.
  • y: Top edge of the text, relative to the canvas.
  • s: Text to draw.
  • color: Colour as 0xRRGGBB.
fn text_width(self: Painter, s: string) -> int#

Returns the displayed terminal-cell width of text.

  • s: Text to measure.
fn paint(app: *App, b: Backend)#

Repaints the whole frame to the backend's pixmap and presents it.

  • app: The application to draw.
  • b: The backend to draw on.
fn run(win: Window)#

Opens the window and runs the event loop until the user closes it or a callback calls ctx.quit(). Prints a note and returns when no X display is reachable.

  • win: The window spec to open and drive.
fn run_once(win: Window) -> bool#

Opens the window, paints one frame, drains any queued events, paints again, and closes. Used by integration tests to exercise the real X pipeline headlessly. Returns false when no display is reachable.

  • win: The window spec to open, paint, and close.