Writing a ReefVision app

ReefVision is a pure-Reef terminal UI toolkit (no curses). Host I/O goes through reef_rv_* via reefvision.term. Widgets are passive objects: View → Group → Window / Desktop. Insertion is win.add(child). Hold the widgets you construct; there is no widget id.

API detail lives in the ReefVision stdlib reference, starting at reefvision.desktop. Extra patterns and demos: ReefVision notes.

Prerequisites

ReefVision ships in the stdlib of the Reef binary release; there is nothing extra to build. Unpack the release for your platform (the INSTALL.md at the root of the tarball has the per-OS steps, including macOS) and check the compiler:

tar xzf reef-lang-<version>-linux-amd64.tar.gz
cd reef-lang-<version>-linux-amd64
./bin/reefc --version

Run it in place as ./bin/reefc, or run ./install.sh once and use reefc from your PATH. reefc compiles through C, so you also need a C compiler and linker (clang or gcc).

step and run are reserved (for step; Active Object run()). The application loop is do_step / run_loop. InputLine takes three arguments (bounds, max_len, initial) — Reef has no overloads.

Minimal loop

module main

import reefvision.cell as cell
import reefvision.desktop as desktop
import reefvision.event as event
import reefvision.theme as theme
import reefvision.view as view
import reefvision.widgets.button as button
import reefvision.widgets.inputline as inputline
import reefvision.widgets.label as label
import reefvision.widgets.messagebox as msgbox
import reefvision.widgets.window as window

fn main(): int
    let app = new desktop.Application(theme.default_theme())
    let win = new window.Window(cell.rect(10, 5, 58, 12), "ReefVision Hello")
    let name = new inputline.InputLine(cell.rect(11, 1, 40, 1), 48, "")

    win.add(new label.Label(cell.rect(2, 1, 8, 1), "Name:"))
    win.add(name)
    win.add(new button.Button(cell.rect(2, 4, 12, 1), "OK", event.CMD_OK()))
    win.add(new button.Button(cell.rect(16, 4, 12, 1), "Quit", event.CMD_QUIT()))
    app.desktop.add(win)

    if not app.open()
        println("not a TTY")
        return 1
    end if

    while app.running
        app.do_step()
        if app.last_command == event.CMD_OK()
            app.clear_command()
            let _ = app.message_box("Greeting", "Hello, ${name.text}!", msgbox.messagebox_kind_ok())
        elif app.last_command == event.CMD_QUIT()
            app.quit()
        end if
    end while
    app.close()
    return 0
end main

end module

import reefvision.view is required so Group.add(v: View) accepts a Label / Button / InputLine. run_loop is open + while running() { do_step() } + close; it quits when last_command is CMD_QUIT. Use the explicit do_step loop when you inspect last_command() as above — CMD_QUIT does not stop the loop by itself, so call app.quit() after optional confirm. Always clear_command() before acting. message_box / modal_run return the dialog result; they do not leave it in last_command (otherwise a Greeting box whose OK is CMD_OK would reopen forever).

Save it as app.reef, then build and run:

reefc app.reef -o app   # ./bin/reefc when running in place
./app                   # must be a TTY

Full demo with F2 theme cycling and an optional TOML path: share/reef/examples/reefvision_hello.reef in the release tree.

Subclassed window

Command ids stay (CMD_OK, CMD_QUIT, …; 0 = none, 1–99 toolkit, 100+ app). Override handle_event and return view.EventResult. Closures that capture widgets across do_step() are the wrong tool; subclass or use a command id.

module main

import reefvision.cell as cell
import reefvision.event as event
import reefvision.view as view
import reefvision.widgets.button as button
import reefvision.widgets.inputline as inputline
import reefvision.widgets.window as window

object HelloWin extends window.Window
    name: inputline.InputLine

    init(name: inputline.InputLine)
        inherited init(cell.rect(10, 5, 58, 12), "Hello")
        self.name = name
        self.add(name)
        self.add(new button.Button(cell.rect(2, 4, 12, 1), "OK", event.CMD_OK()))
    end init

    override exclusive fn handle_event(e: event.Event): view.EventResult
        let r = inherited handle_event(e)
        if r.command == event.CMD_OK()
            return view.event_handled()
        end if
        return r
    end handle_event
end HelloWin

proc main()
    let name = new inputline.InputLine(cell.rect(11, 1, 40, 1), 48, "")
    let _win = new HelloWin(name)
end main

end module

inherited init(...) is the first statement of init. event_handled() / event_command(cmd) / event_ignored() construct EventResult { handled, command }.

Building blocks

Module Role
desktop Application (passive) and Desktop; do_step / run_loop, status, modals, paste
view View, Group, EventResult; focus; virtual draw / handle_event
theme Palettes, frame mode (single/double/ascii), TOML load
widgets.* Label, Button, InputLine, ListBox, ComboBox, Checkbox, RadioGroup, ProgressBar, TextArea, Table, MenuBar, MessageBox, Window, Scrollbar, Clock
event / input Keys, mouse, bracketed paste
commands Enable/disable + accelerators

Application is not an Active Object. The TTY loop is one thread.

reefvision.widgets.filereq.FileReq is an open/save requester (Name, Files list, Parent, OK, Cancel). Directories are prefixed ▸ ; double-click or Enter on a directory enters it. Push it with modal_push / modal_run and read selected_path() on OK.

reefvision.widgets.combo.ComboBox is a one-line field with a scrollable dropdown (add_item / selected / selected_text). The list paints in the Group/Window overlay pass so it can sit over widgets below the field.

Frame-integrated scrollbars (draw_frame_scrollbar / draw_frame_h_scrollbar) open the window border (┴█┬ vertically, ┤█├ horizontally) with a one-cell thumb. TextArea uses those helpers.

TextArea.set_syntax(syntax.LANG_REEF()) colors keywords, strings, comments, and numbers. syntax.lang_from_path maps .reef / .toml / .c / .sh. TextArea.set_syntax_scheme(syntax.scheme_by_name("darcula")) picks the editor palette (default, darcula, highcontrast, paper, monokai). Chrome (frames, menus, status) still follows the UI theme.

Right-click: override Window.context_menu() to return a PopupMenu for that window. If the menu is empty, the desktop popup from Application.set_context_menu is used.

Patterns that matter

  1. Client coordinates — child bounds on a Window are relative to the client (inside the frame). Outer size is client + 2 in each dimension.
  2. Locals, not ids — keep the InputLine / ListBox you constructed and call .text / .selected. There is no public widget id.
  3. Commands — toolkit IDs 1–99 (CMD_OK, CMD_QUIT, …); app commands ≥ 100. Register with commands_set; Button stores a command id that Group bubbles into Application.last_command.
  4. Modals — app.message_box(title, text, kind) for simple dialogs; modal_push / modal_run / modal_poll for wizards (see installer).
  5. Paste — bracketed paste is enabled on fullscreen open; text goes to the focused InputLine or TextArea.
  6. Custom widgets — object Gauge extends view.View, override draw, win.add(new Gauge(...)). Example: share/reef/examples/reefvision_gauge.reef.

Reference demos

The release tree ships these under share/reef/examples/.

Demo Path What it stresses
Hello reefvision_hello.reef InputLine local, OK/Quit, MessageBox, F2 theme
App shell reefvision_app_shell.reef Menus, multi-window, themes, forms, context menu
Installer reefvision_installer.reef Modal stack, ProgressBar
Mini IDE reefvision_ide.reef TextArea + Table
Gauge reefvision_gauge.reef User View subclass

They need a TTY. From the release tree:

reefc share/reef/examples/reefvision_hello.reef -o rv_hello && ./rv_hello

API docs

Start at reefvision.desktop and the widget pages under reefvision.widgets in the stdlib reference.

Themes

Built-ins: theme.theme_builtin_at / F2 cycle. Theme TOML files ship with the stdlib under lib/reef/stdlib/reefvision/themes/*.toml in the release tree (or your install prefix). Copy one next to your app, or pass its full path:

module main

import reefvision.desktop as desktop
import reefvision.theme as theme

proc main()
    mut app = new desktop.Application(theme.default_theme())
    let th = theme.theme_load_file("green.toml")
    if theme.theme_parse_ok()
        app.set_theme(th)
    end if
end main

end module