Slide 64 de 74

Views

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 interface que dá para testar
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, cada um recebendo primeiro seus atributos e depois o que mostra — exceto spacer() e none(), que não recebem nada, e spinner(attrs), que recebe só atributos.

  • Layout: row(attrs, views), column(attrs, views), e overlay(attrs, child) — um diálogo centralizado e escurecido, ou, com um align ou justify, um painel preso a uma borda que deixa os cliques passarem.
  • Mostrar: text(attrs, content), image(attrs, src, alt), icon(attrs, Icon), link(attrs, href, label), table(attrs, rows) para uma Table, e canvas para desenhar, no seu próprio slide.
  • Pedir: button(attrs, label), input(attrs, value), textarea(attrs, value), checkbox(attrs, label, checked), select(attrs, options, chosen).

Os atributos também são um conjunto fechado: gap, pad, width, height, grow, align, justify e scroll cuidam do layout — um width é limitado ao espaço que houver — size, heading, tone e background do estilo, e disabled, busy (que também desabilita), placeholder, hint e kind descrevem o estado. Um que não se aplica a um widget é ignorado. As enumerações por trás deles são conjuntos fechados alcançados como qualquer tipo da biblioteca: Align é Start, Center, End ou Stretch; Justify é Start, Center, End ou Between; TextSize é Title, Subtitle, Body ou Caption; Axis é Horizontal, Vertical ou Both; um InputKind é Text, Number, Date, Password, Search ou Range, um controle deslizante de 0 a 100; um Tone é Normal, Muted, Good, Warn ou Danger, ou uma cor exata por HEX e RGBA — um HEX malformado vira Normal e um canal RGBA é limitado à faixa; e um Icon é um de dezoito, então um nome que não seja um deles é erro de compilação.

html(view) renderiza uma árvore como um fragmento HTML e page(title, view) como um documento inteiro com a folha de estilo embutida, claro e escuro — o que um handler de httpServe pode responder diretamente, e o que toda tela deste tour, exceto os exemplos de código, é. Numa tela de toque, os controles crescem até a largura de um polegar e o texto de um campo até 16 pixels. Como uma view é um valor, uma interface pode ser testada sem navegador: um teste renderiza uma com html e lê a marcação, que é o lugar mais barato para conferir que um botão desabilitado está desabilitado e que o texto de outra pessoa não consegue contrabandear um script para a página.