Slide 44 de 53
O módulo de UI
Uma página, servida diretamente
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)], "Tour do Hive"),
hive.ui.row([hive.ui.gap(8)], [
hive.ui.input([hive.ui.placeholder("seu nome")], ""),
hive.ui.button([hive.ui.tone(hive.ui.Tone.Good())], "Dizer oi"),
]),
hive.ui.link([hive.ui.background(hive.ui.Tone.Good())], "/1", "Começar →"),
])
body := hive.ui.page("Hive", view) // um documento inteiro, servido diretamente
echo len(body)
}
Uma window, sem tipo de comando
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())], "Atualizar")]
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)) // lá vai; nada a segurar
return Model(model.rows, true) // ...e o spinner começa agora
} else if msg is Msg.Loaded(rows) {
return Model(rows, false) // volta como uma mensagem
}
return model
}
proc reload(window: hive.syslink.Address): void {
hive.task.sleep(400)
async window(Msg.Loaded(["north", "south"]))
}
proc main(): void {
ui.window("Colônias", view, update, Model([], false))
}
Uma interface que dá para testar
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("diga algo")], model.draft),
ui.button([ui.disabled(model.draft == "")], "Publicar"),
]
for each post in model.posts {
append(kids, ui.text([], post))
}
return ui.column([ui.pad(24), ui.gap(8)], kids)
}
test "o botão fica desabilitado até algo ser digitado" {
empty := ui.html(view(Model([], "")))
assert indexOf(empty, "disabled") is Result.Ok(_)
typed := ui.html(view(Model([], "oi")))
assert indexOf(typed, "disabled") is Result.Error(_)
}
test "o corpo de um post não contrabandeia markup para a página" {
page := ui.html(view(Model(["<script>steal()</script>"], "")))
assert indexOf(page, "<script>") is Result.Error(_)
}
hive.ui constrói uma interface como um valor: uma View é uma árvore de widgets, e cada um deles recebe as mesmas duas coisas, seus atributos e depois sua própria carga. row e column organizam filhos; text, image, icon e link mostram algo; button, input, textarea, checkbox e select pedem algo; table mostra uma Table e canvas desenha sobre um plano, e spacer, spinner, none e overlay completam o conjunto. Os atributos são um conjunto igualmente fechado: gap, pad, width, height, grow, align, justify e scroll organizam; size, heading, tone e background estilizam; disabled, busy, placeholder, hint e kind descrevem estado; e on, onDismiss, onInput, onSubmit, onChoose, onToggle, onPick e onSort carregam um evento, para uma window e não para este tipo de página. Align, Justify, TextSize, Tone, Axis, InputKind e Icon são as enumerações por trás deles, acessadas como qualquer outro tipo da biblioteca.html(view) renderiza uma árvore como um fragmento HTML, e page(title, view) como um documento inteiro com uma stylesheet já embutida, claro e escuro igualmente — que é exatamente o que um handler de httpServe pode usar para responder a uma requisição diretamente, e o que toda tela deste tour, exceto suas amostras de código, é. A mesma árvore, mostrada em vez disso por window(title, view, update, state), se torna uma página viva: update é um fold sobre seus eventos, a mesma forma que um handler de hive.syslink faz fold sobre uma caixa de mensagens, então uma window tem um endereço e não precisa de mutex próprio.Esse endereço é também por que não existe um tipo de comando. Toda linguagem construída assim precisa de uma resposta para "o update quer buscar algo, e não pode travar a tela enquanto busca", e a maioria delas inventa um tipo para isso — um Cmd, uma task, um efeito que o runtime interpreta. Hive não precisa de nenhum, porque já tem duas coisas que somadas dão uma: update é um proc, então pode agir, e hive.syslink.self(envelope) é o endereço da própria window, então o trabalho iniciado com async pode mandar sua resposta de volta como uma mensagem comum.E como uma view é um valor e não um template, uma interface dá para testar sem navegador. html é o mesmo renderizador que uma página servida usa, então um teste pode renderizar uma view e ler o markup que sai — que é o lugar mais barato que existe para conferir que um botão desabilitado está mesmo desabilitado, e que o texto de outra pessoa não consegue contrabandear uma tag de script para dentro da sua página.Dois widgets ficaram de fora de tudo isso, e cada um tem um slide só para si: canvas, que desenha sobre um plano e que uma página servida desenha exatamente como uma window desenha, e scene, que desenha em três dimensões e que somente uma window consegue desenhar.