type Msg {
Tick { ms: Int }
}
func view(angle: Float): hive.ui.View {
return hive.ui.scene(
[
hive.ui.grow(1),
hive.ui.eye(0.0, 1.5, 5.0),
hive.ui.aim(0.0, 0.0),
hive.ui.onFrame(Msg.Tick(_)),
],
[
hive.ui.ground([hive.ui.paint(hive.ui.Tone.Muted())], 10.0, 10.0),
hive.ui.box(
[hive.ui.at(0.0, 1.0, 0.0), hive.ui.turn(0.0, angle, 0.0), hive.ui.paint(hive.ui.Tone.Good())],
1.0, 1.0, 1.0,
),
],
)
}
// O onFrame de uma scene reporta quantos milissegundos o último frame levou,
// então o giro que esta caixa faz é em segundos reais, não em frames — a
// mesma caixa gira na mesma velocidade a trinta quadros por segundo ou a sessenta.
proc spin(angle: mut Float, msg: Msg): Msg {
if msg is Msg.Tick(ms) {
angle += hive.conv.itf(ms) * 0.002
}
return msg
}
proc main(): void {
spawn(hive.ui.window("Uma caixa girando", view, spin), 0.0)
}type Msg {
Tick { ms: Int }
Padded { pad: Int, control: Str, at: Float }
}
type Kart {
x: Float
z: Float
steer: Float
throttle: Float
}
func view(kart: Kart): hive.ui.View {
return hive.ui.scene(
[
hive.ui.grow(1),
hive.ui.eye(kart.x, 4.0, kart.z + 9.0),
hive.ui.aim(0.0, -0.25),
hive.ui.lens(70),
hive.ui.onFrame(Msg.Tick(_)),
hive.ui.onPad(Msg.Padded(_, _, _)),
],
[
hive.ui.ground(
[hive.ui.paint(hive.ui.Tone.HEX("#61686f")), hive.ui.surface(hive.ui.Surface.Tarmac())],
120.0, 120.0,
),
hive.ui.box(
[
hive.ui.at(kart.x, 0.4, kart.z),
hive.ui.paint(hive.ui.Tone.HEX("#00d2be")),
hive.ui.surface(hive.ui.Surface.Gloss()),
],
1.6, 0.6, 3.4,
),
],
)
}
// Um analógico e um gatilho chegam pelo mesmo buraco e são distinguidos pelo
// nome, porque um botão é um comando que só é 0 ou 1. Nada é guardado aqui
// que o controle não tenha dito: onPad reporta o que *mudou*, então o modelo
// lembra a última posição de cada comando que lhe interessa.
proc drive(kart: mut Kart, msg: Msg): Msg {
if msg is Msg.Padded(pad, control, at) {
if control == "leftX" { kart.steer = at }
if control == "rt" { kart.throttle = at }
} else if msg is Msg.Tick(ms) {
step := hive.conv.itf(ms) / 1000.0
kart.x = kart.x + kart.steer * kart.throttle * 16.0 * step
kart.z = kart.z - kart.throttle * 16.0 * step
}
return msg
}
proc main(): void {
spawn(hive.ui.window("Um controle", view, drive), Kart(0.0, 0.0, 0.0, 0.0))
}Alguns atributos vão na cena e não numa forma:
eye(x, y, z) é onde a câmera está, aim(yaw, pitch) para onde ela olha, em radianos, e lens(Int) o quanto ela abrange, em graus. fog(Int) é o quão longe se enxerga, em metros, e background(Tone) o céu em que ela se dissolve, desenhado como uma faixa até o horizonte.shadows(false) desliga as sombras do sol. Elas cobrem uns cinquenta metros em volta da câmera, um ground as recebe mas não projeta nenhuma, e ligá-las ou desligá-las reconstrói todo shader, então não é algo para mudar a cada quadro. A iluminação é fixa fora isso, escolhida para que uma cor saia daquela cor com um lado sombreado: o que deixa uma cena escura é a paleta.grain(px) desenha o mundo nessa quantidade de pixels no lado curto e o amplia de volta sem filtro, e bits(n) permite a cada canal de cor essa quantidade de bits, com pontilhado em vez de arredondamento — então cinco é a cor de quinze bits dos portáteis antigos.grab(Bool) pede à janela para prender o mouse, e crosshair(Bool) desenha uma mira.Os eventos são da própria cena: onFrame os milissegundos que o último quadro levou — um quadro em andamento por vez, um intervalo informado como no máximo 100 — onRate a taxa de atualização da tela em hertz, entre 20 e 480 e mudando no máximo uma vez por segundo, onKeyDown e onKeyUp o nome de uma tecla, onLook o quanto o mouse andou, onGrab se a janela tem o ponteiro, onSize a largura e a altura da caixa quando ela começa e sempre que muda, e onPad um controle. Uma tecla é nomeada por si mesma quando é uma letra ou um dígito, e senão space, enter, escape, tab, shift, ctrl, up, down, left ou right.
Um navegador não tem evento para controle, então o runtime lê os controles uma vez por quadro e envia o que mudou: onPad informa qual controle, qual comando e onde ele está agora. Os analógicos, leftX leftY rightX rightY, vão de -1 a 1, e os gatilhos lt e rt de 0 a 1; a b x y lb rb up down left right back start guide lstick rstick são botões, 0 ou 1; qualquer outra coisa é informada por número. here é o próprio controle — 1 quando chega, 2 com um layout que o navegador não soube nomear, 0 quando sai, depois de soltar tudo o que segurava. Movimentos pequenos dentro de uma zona morta não são enviados. Um navegador não mostra controle nenhum até um ser tocado, e um instalado como Flatpak precisa de acesso ao dispositivo.
inset(attrs, views) mostra o mesmo mundo a partir do seu próprio eye, aim e lens — um retrovisor, uma janela de replay — disposto sobre ele como uma coluna, compartilhando céu, neblina e sombras, e desenhado só onde fica sobre a cena. Uma janela desenha uma cena e quantos insets quiser. Uma página servida mostra a caixa de uma cena vazia, então estes exemplos são só código.