Slide 44 of 53
The UI module
A page, served directly
proc main(): void {
view := hive.ui.column([hive.ui.pad(24), hive.ui.gap(12)], [
hive.ui.text([hive.ui.size(hive.ui.TextSize.Title()), hive.ui.heading(1)], "Hive Tour"),
hive.ui.row([hive.ui.gap(8)], [
hive.ui.input([hive.ui.placeholder("your name")], ""),
hive.ui.button([hive.ui.tone(hive.ui.Tone.Good())], "Say hi"),
]),
hive.ui.link([hive.ui.background(hive.ui.Tone.Good())], "/1", "Start →"),
])
body := hive.ui.page("Hive", view) // a whole document, served directly
echo len(body)
}
A window, with no command type
import hive.ui
type Model {
rows: Str[dyn]
loading: Bool
}
type Msg {
Refresh
Loaded { rows: Str[dyn] }
}
func view(model: Model): ui.View {
mut ui.View[dyn] kids = [ui.button([ui.on(Msg.Refresh())], "Refresh")]
if model.loading {
append(kids, ui.spinner([]))
}
for each row in model.rows {
append(kids, ui.text([], row))
}
return ui.column([ui.pad(24), ui.gap(8)], kids)
}
proc update(model: Model, msg: Msg, from: hive.syslink.Envelope): Model {
if msg is Msg.Refresh {
async reload(hive.syslink.self(from)) // off it goes; nothing to hold
return Model(model.rows, true) // ...and the spinner starts now
} else if msg is Msg.Loaded(rows) {
return Model(rows, false) // it comes back as a message
}
return model
}
proc reload(window: hive.syslink.Address): void {
hive.task.sleep(400)
async window(Msg.Loaded(["north", "south"]))
}
proc main(): void {
ui.window("Colonies", view, update, Model([], false))
}
An interface you can test
import hive.ui
type Model {
posts: Str[dyn]
draft: Str
}
func view(model: Model): ui.View {
mut ui.View[dyn] kids = [
ui.input([ui.placeholder("say something")], model.draft),
ui.button([ui.disabled(model.draft == "")], "Post"),
]
for each post in model.posts {
append(kids, ui.text([], post))
}
return ui.column([ui.pad(24), ui.gap(8)], kids)
}
test "the post button is disabled until something is typed" {
empty := ui.html(view(Model([], "")))
assert indexOf(empty, "disabled") is Result.Ok(_)
typed := ui.html(view(Model([], "hi")))
assert indexOf(typed, "disabled") is Result.Error(_)
}
test "a post body cannot smuggle markup into the page" {
page := ui.html(view(Model(["<script>steal()</script>"], "")))
assert indexOf(page, "<script>") is Result.Error(_)
}
hive.ui builds an interface as a value: a View is a tree of widgets, and every one of them takes the same two things, its attributes and then its own payload. row and column lay out children; text, image, icon and link show something; button, input, textarea, checkbox and select ask for something; table shows a Table and canvas draws on a plane, and spacer, spinner, none and overlay round out the set. Attributes are just as closed a set: gap, pad, width, height, grow, align, justify and scroll lay out; size, heading, tone and background style; disabled, busy, placeholder, hint and kind describe state; and on, onDismiss, onInput, onSubmit, onChoose, onToggle, onPick and onSort carry an event, for a window rather than this kind of page. Align, Justify, TextSize, Tone, Axis, InputKind and Icon are the enumerations behind them, reached like any other library type.html(view) renders a tree as an HTML fragment, and page(title, view) as a whole document with a stylesheet already built in, light and dark alike — which is exactly what an httpServe handler can answer a request with directly, and what every screen in this tour but its code samples is. The same tree, shown instead by window(title, view, update, state), becomes a live page: update is a fold over its events, the same shape a hive.syslink handler folds over a mailbox, so a window has an address and needs no mutex of its own.That address is also why there is no command type. Every language built this way needs an answer to "the update wants to fetch something, and must not block the screen while it does", and most of them invent a type for it — a Cmd, a task, an effect the runtime interprets. Hive needs none, because it already has two things that add up to one: update is a proc, so it may act, and hive.syslink.self(envelope) is the window's own address, so work started with async can send its answer back as an ordinary message.And because a view is a value rather than a template, an interface is testable without a browser. html is the same renderer a served page uses, so a test can render a view and read the markup that comes out — which is the cheapest place there is to check that a disabled button really is disabled, and that somebody else's text cannot smuggle a script tag into your page.Two widgets are missing from all of that, and each has a slide to itself: canvas, which draws on a plane and which a served page draws exactly as a window does, and scene, which draws in three dimensions and which only a window can draw.