Construa
Sua primeira tela
Escreva uma tela em TSX, verifique e converta, veja no QEMU e em um Raspberry Pi 5, e depois controle a tela a partir de Rust: suas ações, campos, slots e grupos.
Escreva uma tela como você faria em React. O Screen Compiler verifica a tela contra o seu subconjunto e a transforma em Rust uma única vez, em tempo de build, na sua máquina. O que chega ao dispositivo é esse Rust, embutido na imagem do sistema: sem JavaScript, sem navegador, sem interpretador. Este guia leva uma tela de um arquivo TSX, passando pelo QEMU, até um Raspberry Pi 5, e depois mostra como uma aplicação controla uma tela. Telas a partir de React lista tudo o que é convertido.
Preparação
Clone o NoWayOS, o Screen Compiler e o Showcase lado a lado, e construa o contêiner de desenvolvimento uma vez. Todo alvo do make roda dentro dele, então nada mais é instalado na sua máquina; Primeiros passos diz do que o contêiner precisa.
mkdir nowayos-work && cd nowayos-work
git clone https://github.com/NoWayOS/nowayos.git
git clone https://github.com/NoWayOS/screen-compiler.git
git clone https://github.com/NoWayOS/showcase.git
# the NoWayOS the Showcase builds on
git -C nowayos checkout $(cat showcase/nowayos.rev)
make -C nowayos container
O Showcase é a demonstração com que um Pi inicializa. A sua React gallery mostra cada componente que o Screen Compiler converte, uma tela por vez, e a sua primeira tela entra nela.
Escreva uma tela
Salve isto como showcase/showcase/showcase-app/tsx/gallery-hello.tsx:
import { Button, Column, HeaderBar, Screen, Spacer, Text } from "nowayos:kit";
type HelloProps = {
onHome: () => void;
onBack: () => void;
onNext: () => void;
onWave: () => void;
};
export function GalleryHelloScreen(props: HelloProps) {
return (
<Screen>
<Column gap={12} padding={16}>
<HeaderBar gap={8} padding={8}>
<Button variant="secondary" onPress={props.onHome}>Home</Button>
<Text role="heading">Hello</Text>
<Spacer />
<Button variant="secondary" onPress={props.onBack}>Back</Button>
<Button onPress={props.onNext}>Next</Button>
</HeaderBar>
<Text role="title">Hello from TSX</Text>
<Text>Written in React's syntax, compiled to Rust at build time.</Text>
<Button onClick={() => props.onWave()}>Wave</Button>
</Column>
</Screen>
);
}
Cada prop de callback vira uma ação, numerada a partir de 1 na ordem em que a tela as usa. Toda tela da galeria começa com a mesma barra, Home, Back e Next, para que o Showcase trate essas três do mesmo jeito em todas as telas; onWave é só desta tela, e aqui nada a trata.
Verifique
cd screen-compiler
make run ARGS="check ../showcase/showcase/showcase-app/tsx/gallery-hello.tsx"
Uma tela que converte recebe uma linha de volta:
../showcase/showcase/showcase-app/tsx/gallery-hello.tsx: supported (1 screen, 10 elements, 0 design tokens, 4 actions)
Tudo o que fica fora do subconjunto é recusado onde está escrito, com o que escrever no lugar, e nada é convertido. Dê à coluna padding="1rem" e ao botão um onClick que registra algo no log, e a verificação diz:
../showcase/showcase/showcase-app/tsx/gallery-hello.tsx: not supported
../showcase/showcase/showcase-app/tsx/gallery-hello.tsx:13:32: error[unsupported]: `padding` in `rem` is not supported: lengths are layout units, written in px
help: write padding="16px", padding={16} or a design token
../showcase/showcase/showcase-app/tsx/gallery-hello.tsx:23:26: error[unsupported]: a function here may only call a callback prop, with no arguments
help: pass a callback prop of the component: onClick={onContinue}, or onClick={() => props.onContinue()}
../showcase/showcase/showcase-app/tsx/gallery-hello.tsx: 2 errors
Inclua na galeria
cd ../showcase
make screens-bless
converte cada uma das telas do Showcase com o Screen Compiler ao lado e grava o Rust em showcase/showcase-app/src/generated/: gallery-hello.rs, e uma função que a constrói, generated::build_gallery_hello. Depois, em showcase/showcase-app/src/lib.rs, inclua a sua tela no fim da galeria:
pub const GALLERY: [(&str, generated::Build); 12] = [
// ... the gallery's own eleven screens ...
("gallery-hello", generated::build_gallery_hello),
];
e na lista logo abaixo, a das telas cuja barra é verificada: gallery_hello::gallery_hello_screen, com gallery_hello no use dessa lista. A verificação roda enquanto o Showcase compila, então uma barra fora de ordem interrompe o build.
Veja no QEMU
make qemu-test SYSTEM=showcase-gallery
reproduz a volta roteirizada pela galeria e compara cada uma das telas dela, pixel a pixel, com a sua imagem aprovada: o resto da galeria continua sendo desenhado como foi aprovado. Para ver a sua:
make qemu SYSTEM=showcase-gallery VNC=1
A tela do QEMU é publicada via VNC em localhost:5900 na sua máquina, com uma senha impressa só para aquela execução; no macOS, abra vnc://localhost:5900. O que você digita no terminal chega ao Showcase: Seta para baixo, Seta para a direita e Enter abrem a React gallery; depois, Shift-Tab e Enter pressionam Back, que dá a volta da primeira tela para a última, a sua. Saia do QEMU com Ctrl-A e depois x.
Veja no Pi
Prepare a placa uma vez, como mostra Rodando em um Raspberry Pi 5: o cartão, a Debug Probe, um monitor ligado antes de energizar a placa e um mouse USB. Depois, a partir de showcase/, na sua máquina e não no contêiner:
make pi-show PI_DEVICE=/dev/cu.usbmodemXXXX SYSTEM=showcase
Isso constrói o Showcase com a sua tela dentro, envia para a placa pela probe e deixa rodando até a placa ser desligada e religada. Clique em React gallery e depois em Back: a sua tela, no monitor HDMI do Pi.
Controle uma tela a partir de Rust
Uma tela diz à aplicação o que aconteceu, e a aplicação diz à tela o que mostrar, de quatro maneiras: ações, campos, slots e grupos. A fixture fixtures/visit-booking.tsx do Screen Compiler usa as quatro:
type VisitBookingProps = {
phoneError: string;
onBack: () => void;
onBook: () => void;
};
// ...
<TextInput id="name" placeholder="Your name" maxLength={NAME_LEN} />
{props.phoneError && <Text>{props.phoneError}</Text>}
<RadioGroup id="when" options={["Morning", "Afternoon", "Evening"]} />
<Checkbox id="remind" label="Remind me the day before" />
<Button id="book" onPress={props.onBook}>Book</Button>
O Rust dela nomeia cada uma, numerada a partir de 1 na ordem em que a tela as escreve:
| No TSX | No Rust | A aplicação |
|---|---|---|
Uma prop de callback, onBook |
Uma ação, actions::BOOK |
é avisada quando o botão é pressionado |
Um controle com um id, id="name" |
Um campo, fields::NAME |
lê o que foi digitado |
Uma prop escrita como texto, {props.phoneError} |
Um slot, slots::PHONE_ERROR |
define o texto |
Uma prop que decide o que aparece, {props.phoneError && …} |
Um grupo, groups::PHONE_ERROR |
mostra ou esconde o grupo |
A aplicação constrói a tela em uma árvore de interface do NoWayOS, define os slots e os grupos dela e entrega à árvore cada evento de entrada. Um toque em Book volta como a ação de Book, e então a aplicação lê os campos:
let mut tree = Tree::<64>::new();
let screen = visit_booking::build(&mut tree)?;
tree.set_root(screen.root)?;
// A slot is text the application sets; a group is content it shows or hides.
tree.set_slot(slots::PHONE_ERROR, "Digits only, please")?;
tree.set_visible(groups::PHONE_ERROR, true)?;
// Every input event goes to the tree; a tap on Book comes back as its action.
match tree.handle(event) {
Some(UiEvent::Action(actions::BOOK)) => {
let name = tree.value(fields::NAME).unwrap_or("");
let when = tree.selected(fields::WHEN);
let remind = tree.checked(fields::REMIND).unwrap_or(false);
booked = Some((name.to_owned(), when, remind));
}
Some(UiEvent::Action(actions::BACK)) => back = true,
_ => {}
}
Este é o código de um teste do repositório do Screen Compiler, contract-check/tests/driving.rs, que constrói a tela convertida sobre o próprio núcleo de interface do NoWayOS e toca em Book no make ci dele. Em um dispositivo, os eventos vêm do intermediário de entrada do NoWayOS, e a árvore é desenhada pelo renderizador dele. Uma comparação como props.step === "review" vira, do mesmo jeito, um switch, switches::STEP, que a aplicação define com set_case.
E agora
Telas a partir de React traz cada componente, prop e propriedade de estilo que o Screen Compiler aceita. Para trazer uma aplicação React existente, comece por Portar a partir de React e pelo relatório de cobertura.