Construire
Votre premier écran
Écrire un écran en TSX, le vérifier et le convertir, le voir dans QEMU et sur un Raspberry Pi 5, puis le piloter depuis Rust : ses actions, ses champs, ses slots et ses groupes.
Écrivez un écran comme vous le feriez en React. Le Screen Compiler le vérifie au regard de son sous-ensemble et le transforme en Rust une seule fois, à la compilation, sur votre machine. Ce qui arrive sur l’appareil, c’est ce Rust, intégré à l’image système : pas de JavaScript, pas de navigateur, pas d’interpréteur. Ce guide emmène un écran d’un fichier TSX jusqu’à un Raspberry Pi 5 en passant par QEMU, puis montre comment une application pilote un écran. Des écrans issus de React liste tout ce qui se convertit.
Mise en place
Clonez NoWayOS, le Screen Compiler et le Showcase côte à côte, et construisez une fois le conteneur de développement. Chaque cible make s’exécute à l’intérieur, si bien que rien d’autre n’est installé sur votre machine ; Premiers pas indique ce dont le conteneur a besoin.
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
Le Showcase est la démonstration sur laquelle un Pi démarre. Sa React gallery montre chaque composant que le Screen Compiler convertit, un écran à la fois, et votre premier écran vient s’y ajouter.
Écrire un écran
Enregistrez ceci sous 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>
);
}
Chaque prop de rappel devient une action, numérotée à partir de 1 dans l’ordre où l’écran les utilise. Chaque écran de la galerie commence par la même barre, Home, Back et Next, si bien que le Showcase traite ces trois-là de la même façon sur chaque écran ; onWave est propre à cet écran, et ici rien ne la traite.
Le vérifier
cd screen-compiler
make run ARGS="check ../showcase/showcase/showcase-app/tsx/gallery-hello.tsx"
Un écran qui se convertit reçoit une ligne en retour :
../showcase/showcase/showcase-app/tsx/gallery-hello.tsx: supported (1 screen, 10 elements, 0 design tokens, 4 actions)
Tout ce qui sort du sous-ensemble est refusé là où c’est écrit, avec ce qu’il faut écrire à la place, et rien n’est converti. Donnez à la colonne padding="1rem" et au bouton un onClick qui journalise, et la vérification répond :
../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
L’ajouter à la galerie
cd ../showcase
make screens-bless
convertit chacun des écrans du Showcase avec le Screen Compiler voisin et écrit le Rust dans showcase/showcase-app/src/generated/ : gallery-hello.rs, et une fonction qui le construit, generated::build_gallery_hello. Puis, dans showcase/showcase-app/src/lib.rs, ajoutez votre écran à la fin de la galerie :
pub const GALLERY: [(&str, generated::Build); 12] = [
// ... the gallery's own eleven screens ...
("gallery-hello", generated::build_gallery_hello),
];
et à la liste juste en dessous, celle des écrans dont la barre est vérifiée : gallery_hello::gallery_hello_screen, avec gallery_hello dans le use de cette liste. La vérification s’exécute pendant la compilation du Showcase, si bien qu’une barre dans le mauvais ordre arrête la construction.
Le voir dans QEMU
make qemu-test SYSTEM=showcase-gallery
joue le tour scripté de la galerie et compare chacun de ses propres écrans, pixel par pixel, à son image approuvée : le reste de la galerie se dessine toujours tel qu’approuvé. Pour voir le vôtre :
make qemu SYSTEM=showcase-gallery VNC=1
L’écran de QEMU est publié en VNC sur le localhost:5900 de votre machine, avec un mot de passe affiché pour cette exécution seulement ; sur macOS, ouvrez vnc://localhost:5900. Ce que vous tapez dans le terminal parvient au Showcase : Flèche bas, Flèche droite et Entrée ouvrent la React gallery, puis Maj-Tab et Entrée appuient sur Back, qui fait le tour du premier écran au dernier, le vôtre. Quittez QEMU avec Ctrl-A, puis x.
Le voir sur le Pi
Préparez le Pi une fois, comme le montre Exécuter sur un Raspberry Pi 5 : sa carte microSD, la Debug Probe, un écran branché avant la mise sous tension et une souris USB. Puis, depuis showcase/, sur votre machine plutôt que dans le conteneur :
make pi-show PI_DEVICE=/dev/cu.usbmodemXXXX SYSTEM=showcase
Cela construit le Showcase avec votre écran, l’envoie à la carte par la sonde et le laisse tourner jusqu’à ce que la carte soit éteinte puis rallumée. Cliquez sur React gallery, puis sur Back : votre écran, sur le moniteur HDMI du Pi.
Piloter un écran depuis Rust
Un écran dit à l’application ce qui s’est passé, et l’application dit à l’écran quoi montrer, de quatre façons : actions, champs, slots et groupes. La fixture fixtures/visit-booking.tsx du Screen Compiler utilise les quatre :
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>
Son Rust nomme chacun, numéroté à partir de 1 dans l’ordre où l’écran les écrit :
| Dans le TSX | Dans le Rust | L’application |
|---|---|---|
Une prop de rappel, onBook |
Une action, actions::BOOK |
est prévenue quand le bouton est pressé |
Un contrôle doté d’un id, id="name" |
Un champ, fields::NAME |
lit ce qui a été saisi |
Une prop écrite comme texte, {props.phoneError} |
Un slot, slots::PHONE_ERROR |
définit le texte |
Une prop qui décide de ce qui s’affiche, {props.phoneError && …} |
Un groupe, groups::PHONE_ERROR |
l’affiche ou le masque |
L’application construit l’écran dans un arbre d’interface NoWayOS, définit ses slots et ses groupes, et transmet à l’arbre chaque événement d’entrée. Un appui sur Book revient sous la forme de l’action de Book, et l’application lit alors les champs :
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,
_ => {}
}
C’est le code d’un test du dépôt du Screen Compiler, contract-check/tests/driving.rs, qui construit l’écran converti sur le cœur d’interface de NoWayOS lui-même et appuie sur Book dans son make ci. Sur un appareil, les événements viennent du courtier d’entrées de NoWayOS, et l’arbre est dessiné par son moteur de rendu. Une comparaison comme props.step === "review" devient de la même façon un switch, switches::STEP, que l’application règle avec set_case.
Et ensuite
Des écrans issus de React présente chaque composant, chaque prop et chaque propriété de style que le Screen Compiler accepte. Pour faire passer une application React existante, commencez par Porter depuis React et son rapport de couverture.