NoWayOS
DE
GitHub
MenüSchließen

Bauen

Ihr erster Screen

Einen Screen in TSX schreiben, prüfen und umwandeln, ihn in QEMU und auf einem Raspberry Pi 5 sehen und ihn dann aus Rust steuern: seine Aktionen, Felder, Slots und Gruppen.

Schreiben Sie einen Screen, wie Sie es in React tun würden. Der Screen Compiler prüft ihn gegen seine Teilmenge und wandelt ihn einmal in Rust um, zur Build-Zeit, auf Ihrem Rechner. Auf das Gerät gelangt dieses Rust, eingebaut in das Systemabbild: kein JavaScript, kein Browser, kein Interpreter. Diese Anleitung führt einen Screen von einer TSX-Datei über QEMU bis auf einen Raspberry Pi 5 und zeigt dann, wie eine Anwendung einen Screen steuert. Screens aus React listet alles auf, was umgewandelt wird.

Einrichten

Klonen Sie NoWayOS, den Screen Compiler und den Showcase nebeneinander und bauen Sie einmal den Entwicklungscontainer. Jedes make-Ziel läuft darin, sodass auf Ihrem Rechner nichts weiter installiert wird; Erste Schritte sagt, was der Container braucht.

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

Der Showcase ist die Demonstration, in die ein Pi bootet. Seine React gallery zeigt jede Komponente, die der Screen Compiler umwandelt, einen Screen nach dem anderen, und Ihr erster Screen kommt hinzu.

Einen Screen schreiben

Speichern Sie dies als 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>
  );
}

Jede Callback-Prop wird zu einer Aktion, nummeriert ab 1 in der Reihenfolge, in der der Screen sie verwendet. Jeder Screen der Galerie beginnt mit derselben Leiste, Home, Back und Next, sodass der Showcase diese drei auf jedem Screen gleich behandelt; onWave gehört nur diesem Screen, und hier behandelt sie nichts.

Prüfen

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

Ein Screen, der umgewandelt wird, erhält eine Zeile zurück:

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

Alles außerhalb der Teilmenge wird dort abgelehnt, wo es steht, mit dem, was stattdessen zu schreiben ist, und nichts wird umgewandelt. Geben Sie der Spalte padding="1rem" und dem Button ein onClick, das etwas protokolliert, und die Prüfung meldet:

../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

wandelt jeden Screen des Showcase mit dem Screen Compiler daneben um und schreibt das Rust nach showcase/showcase-app/src/generated/: gallery-hello.rs und eine Funktion, die ihn baut, generated::build_gallery_hello. Fügen Sie Ihren Screen dann in showcase/showcase-app/src/lib.rs am Ende der Galerie hinzu:

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

und in der Liste direkt darunter, deren Screens auf ihre Leiste geprüft werden: gallery_hello::gallery_hello_screen, mit gallery_hello im use dieser Liste. Die Prüfung läuft, während der Showcase kompiliert wird, sodass eine Leiste in falscher Reihenfolge den Build stoppt.

In QEMU ansehen

make qemu-test SYSTEM=showcase-gallery

spielt die vorgegebene Runde durch die Galerie ab und vergleicht jeden ihrer eigenen Screens Pixel für Pixel mit seinem abgenommenen Bild: Der Rest der Galerie wird weiterhin wie abgenommen gezeichnet. Um Ihren zu sehen:

make qemu SYSTEM=showcase-gallery VNC=1

Der Bildschirm von QEMU wird per VNC auf localhost:5900 Ihres Rechners bereitgestellt, mit einem Passwort, das nur für diesen Lauf ausgegeben wird; öffnen Sie unter macOS vnc://localhost:5900. Was Sie im Terminal tippen, erreicht den Showcase: Pfeil nach unten, Pfeil nach rechts und Enter öffnen die React gallery, dann drücken Shift-Tab und Enter Back, das vom ersten Screen zum letzten weiterläuft, Ihrem. Beenden Sie QEMU mit Ctrl-A, dann x.

Auf dem Pi ansehen

Richten Sie das Board einmal so ein, wie Betrieb auf einem Raspberry Pi 5 es zeigt: seine Karte, die Debug Probe, ein vor dem Einschalten angeschlossener Monitor und eine USB-Maus. Führen Sie dann aus showcase/ heraus aus, auf Ihrem Rechner statt im Container:

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

Das baut den Showcase mit Ihrem Screen darin, sendet ihn über die Probe an das Board und lässt ihn laufen, bis das Board aus- und wieder eingeschaltet wird. Klicken Sie auf React gallery, dann auf Back: Ihr Screen, auf dem HDMI-Monitor des Pi.

Einen Screen aus Rust steuern

Ein Screen teilt der Anwendung mit, was geschehen ist, und die Anwendung teilt dem Screen mit, was er zeigen soll, auf vier Wegen: Aktionen, Felder, Slots und Gruppen. Die Fixture fixtures/visit-booking.tsx des Screen Compilers nutzt alle vier:

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>

Ihr Rust benennt jeden davon, nummeriert ab 1 in der Reihenfolge, in der der Screen sie schreibt:

Im TSX Im Rust Die Anwendung
Eine Callback-Prop, onBook Eine Aktion, actions::BOOK erfährt, wenn der Button gedrückt wird
Ein Bedienelement mit einer id, id="name" Ein Feld, fields::NAME liest, was eingegeben wurde
Eine als Text geschriebene Prop, {props.phoneError} Ein Slot, slots::PHONE_ERROR setzt den Text
Eine Prop, die entscheidet, was erscheint, {props.phoneError && …} Eine Gruppe, groups::PHONE_ERROR zeigt oder verbirgt sie

Die Anwendung baut den Screen in einen UI-Baum von NoWayOS, setzt seine Slots und Gruppen und reicht dem Baum jedes Eingabeereignis weiter. Ein Tippen auf Book kommt als Aktion von Book zurück, und dann liest die Anwendung die Felder:

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

Dies ist der Code eines Tests im Repository des Screen Compilers, contract-check/tests/driving.rs, der den umgewandelten Screen auf dem UI-Kern von NoWayOS selbst baut und in seinem make ci auf Book tippt. Auf einem Gerät kommen die Ereignisse vom Input-Broker von NoWayOS, und der Baum wird von seinem Renderer gezeichnet. Ein Vergleich wie props.step === "review" wird auf dieselbe Weise zu einem Switch, switches::STEP, den die Anwendung mit set_case setzt.

Wie es weitergeht

Screens aus React enthält jede Komponente, jede Prop und jede Stileigenschaft, die der Screen Compiler annimmt. Um eine bestehende React-Anwendung herüberzuholen, beginnen Sie mit Von React portieren und seinem Abdeckungsbericht.