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.