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
- Client coordinates — child
boundson aWindoware relative to the client (inside the frame). Outer size isclient + 2in each dimension. - Locals, not ids — keep the
InputLine/ListBoxyou constructed and call.text/.selected. There is no public widget id. - Commands — toolkit IDs 1–99 (
CMD_OK,CMD_QUIT, …); app commands ≥ 100. Register withcommands_set;Buttonstores a command id thatGroupbubbles intoApplication.last_command. - Modals —
app.message_box(title, text, kind)for simple dialogs;modal_push/modal_run/modal_pollfor wizards (see installer). - Paste — bracketed paste is enabled on fullscreen
open; text goes to the focusedInputLineorTextArea. - Custom widgets —
object Gauge extends view.View, overridedraw,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