NoWayOS
IT
GitHub
MenuChiudi

Costruire

La prima schermata

Scrivere una schermata in TSX, verificarla e convertirla, vederla in QEMU e su un Raspberry Pi 5, poi pilotarla da Rust: le sue azioni, i suoi campi, slot e gruppi.

Si scrive una schermata come in React. Lo Screen Compiler la verifica rispetto al suo sottoinsieme e la trasforma in Rust una sola volta, al momento della build, sulla propria macchina. Sul dispositivo arriva quel Rust, integrato nell'immagine di sistema: niente JavaScript, niente browser, niente interprete. Questa guida porta una schermata da un file TSX, passando per QEMU, fino a un Raspberry Pi 5, poi mostra come un'applicazione pilota una schermata. Schermate da React elenca tutto ciò che si converte.

Preparazione

Clonare NoWayOS, lo Screen Compiler e lo Showcase uno accanto all'altro, e costruire una volta il container di sviluppo. Ogni target di make viene eseguito al suo interno, quindi sulla propria macchina non si installa nient'altro; Per iniziare indica ciò che serve al container.

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

Lo Showcase è la dimostrazione con cui si avvia un Pi. La sua React gallery mostra ogni componente che lo Screen Compiler converte, una schermata alla volta, e la prima schermata si aggiunge lì.

Scrivere una schermata

Salvare questo come 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>
  );
}

Ogni prop di callback diventa un'azione, numerata da 1 nell'ordine in cui la schermata le usa. Ogni schermata della galleria comincia con la stessa barra, Home, Back e Next, così lo Showcase tratta quelle tre allo stesso modo su ogni schermata; onWave è propria di questa schermata, e qui niente la gestisce.

Verificarla

cd screen-compiler
make run ARGS="check ../showcase/showcase/showcase-app/tsx/gallery-hello.tsx"

Una schermata che si converte riceve una riga in risposta:

../showcase/showcase/showcase-app/tsx/gallery-hello.tsx: supported (1 screen, 10 elements, 0 design tokens, 4 actions)

Tutto ciò che è fuori dal sottoinsieme viene rifiutato dove è scritto, con ciò che va scritto al suo posto, e non viene convertito nulla. Dando alla colonna padding="1rem" e al pulsante un onClick che scrive nel log, la verifica risponde:

../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
cd ../showcase
make screens-bless

converte ciascuna delle schermate dello Showcase con lo Screen Compiler accanto e scrive il Rust in showcase/showcase-app/src/generated/: gallery-hello.rs, e una funzione che la costruisce, generated::build_gallery_hello. Poi, in showcase/showcase-app/src/lib.rs, aggiungere la schermata alla fine della galleria:

pub const GALLERY: [(&str, generated::Build); 12] = [
    // ... the gallery's own eleven screens ...
    ("gallery-hello", generated::build_gallery_hello),
];

e all'elenco subito sotto, quello delle schermate di cui si verifica la barra: gallery_hello::gallery_hello_screen, con gallery_hello nello use di quell'elenco. La verifica avviene mentre lo Showcase viene compilato, quindi una barra nell'ordine sbagliato ferma la build.

Vederla in QEMU

make qemu-test SYSTEM=showcase-gallery

esegue il giro scriptato della galleria e confronta ciascuna delle sue schermate, pixel per pixel, con la sua immagine approvata: il resto della galleria continua a essere disegnato come approvato. Per vedere la propria:

make qemu SYSTEM=showcase-gallery VNC=1

Lo schermo di QEMU viene pubblicato via VNC su localhost:5900 della propria macchina, con una password stampata valida solo per quell'esecuzione; su macOS, aprire vnc://localhost:5900. Ciò che si digita nel terminale arriva allo Showcase: Freccia giù, Freccia destra e Invio aprono la React gallery, poi Maiusc-Tab e Invio premono Back, che gira dalla prima schermata all'ultima, la propria. Per uscire da QEMU: Ctrl-A, poi x.

Vederla sul Pi

Preparare la scheda una volta, come mostra Esecuzione su Raspberry Pi 5: la sua microSD, la Debug Probe, un monitor collegato prima dell'accensione e un mouse USB. Poi, da showcase/, sulla propria macchina e non nel container:

make pi-show PI_DEVICE=/dev/cu.usbmodemXXXX SYSTEM=showcase

Questo costruisce lo Showcase con la schermata al suo interno, lo invia alla scheda attraverso la sonda e lo lascia in esecuzione finché la scheda non viene spenta e riaccesa. Cliccare su React gallery, poi su Back: ecco la schermata, sul monitor HDMI del Pi.

Pilotare una schermata da Rust

Una schermata dice all'applicazione che cosa è successo, e l'applicazione dice alla schermata che cosa mostrare, in quattro modi: azioni, campi, slot e gruppi. La fixture fixtures/visit-booking.tsx dello Screen Compiler li usa tutti e quattro:

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>

Il suo Rust dà un nome a ciascuno, numerato da 1 nell'ordine in cui la schermata li scrive:

Nel TSX Nel Rust L'applicazione
Una prop di callback, onBook Un'azione, actions::BOOK viene avvisata quando il pulsante è premuto
Un controllo con un id, id="name" Un campo, fields::NAME legge ciò che è stato inserito
Una prop scritta come testo, {props.phoneError} Uno slot, slots::PHONE_ERROR imposta il testo
Una prop che decide che cosa compare, {props.phoneError && …} Un gruppo, groups::PHONE_ERROR lo mostra o lo nasconde

L'applicazione costruisce la schermata in un albero dell'interfaccia di NoWayOS, ne imposta gli slot e i gruppi e passa all'albero ogni evento di input. Un tocco su Book torna come l'azione di Book, e allora l'applicazione legge i campi:

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,
    _ => {}
}

Questo è il codice di un test nel repository dello Screen Compiler, contract-check/tests/driving.rs, che costruisce la schermata convertita sul nucleo dell'interfaccia di NoWayOS stesso e tocca Book nel suo make ci. Su un dispositivo gli eventi arrivano dall'intermediario degli input di NoWayOS, e l'albero viene disegnato dal suo renderer. Un confronto come props.step === "review" diventa allo stesso modo uno switch, switches::STEP, che l'applicazione imposta con set_case.

E poi

Schermate da React riporta ogni componente, prop e proprietà di stile che lo Screen Compiler accetta. Per portare un'applicazione React esistente, cominciare da Portare da React e dal suo rapporto di copertura.