ビルドする
はじめての画面
TSXで画面を書き、チェックして変換し、QEMUとRaspberry Pi 5で表示してから、Rustから動かします。アクション、フィールド、スロット、グループの使い方です。
画面はReactと同じように書きます。Screen Compilerはそれをサブセットに照らしてチェックし、ビルド時に一度だけ、手元のマシンでRustに変換します。デバイスに届くのはそのRustで、システムイメージに組み込まれています。JavaScriptもブラウザもインタプリタもありません。このガイドでは、一つの画面をTSXファイルからQEMUを経てRaspberry Pi 5まで運び、そのうえでアプリケーションが画面をどう動かすかを示します。変換できるものの一覧はReactから画面を作るにあります。
準備する
NoWayOS、Screen Compiler、Showcaseを同じフォルダに並べてクローンし、開発コンテナを一度ビルドします。makeのターゲットはすべてその中で動くので、手元のマシンにはほかに何もインストールしません。コンテナに必要なものははじめににあります。
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
Showcaseは、Piが起動すると表示されるデモです。そのReact galleryは、Screen Compilerが変換するすべてのコンポーネントを一画面ずつ見せます。はじめての画面もここに加わります。
画面を書く
次の内容を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>
);
}
コールバックpropsはそれぞれアクションになり、画面が使う順に1から番号が付きます。ギャラリーの画面はどれも同じバー、Home、Back、Nextで始まるので、Showcaseはこの三つをどの画面でも同じように扱います。onWaveはこの画面だけのもので、ここでは何も処理しません。
チェックする
cd screen-compiler
make run ARGS="check ../showcase/showcase/showcase-app/tsx/gallery-hello.tsx"
変換できる画面には、一行が返ってきます。
../showcase/showcase/showcase-app/tsx/gallery-hello.tsx: supported (1 screen, 10 elements, 0 design tokens, 4 actions)
サブセットの外にあるものは、書かれた場所で拒否され、代わりに何を書けばよいかが示されます。何も変換されません。列にpadding="1rem"を、ボタンにログを出すonClickを与えると、チェックの結果はこうなります。
../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
このコマンドは、隣にあるScreen CompilerでShowcaseの画面をすべて変換し、Rustをshowcase/showcase-app/src/generated/に書き出します。gallery-hello.rsと、それを組み立てる関数generated::build_gallery_helloです。次に、showcase/showcase-app/src/lib.rsで、画面をギャラリーの最後に加えます。
pub const GALLERY: [(&str, generated::Build); 12] = [
// ... the gallery's own eleven screens ...
("gallery-hello", generated::build_gallery_hello),
];
そのすぐ下にある、バーをチェックする画面の一覧にも加えます。gallery_hello::gallery_hello_screenを加え、その一覧のuseにgallery_helloを入れます。このチェックはShowcaseのコンパイル中に行われるので、順序の違うバーがあるとビルドが止まります。
QEMUで見る
make qemu-test SYSTEM=showcase-gallery
このコマンドは、台本どおりにギャラリーを一巡し、ギャラリー自身の画面を一つずつ、ピクセル単位で承認済みの画像と照合します。ギャラリーのほかの画面が承認どおりに描かれていることがわかります。自分の画面を見るには、次を実行します。
make qemu SYSTEM=showcase-gallery VNC=1
QEMUの画面は、手元のマシンのlocalhost:5900でVNC経由で公開され、その実行限りのパスワードが表示されます。macOSではvnc://localhost:5900を開いてください。ターミナルで打ったキーはShowcaseに届きます。下矢印、右矢印、EnterでReact galleryが開き、続けてShift-TabとEnterでBackが押されます。Backは最初の画面から最後の画面、つまりあなたの画面へ回り込みます。QEMUを終了するには、Ctrl-Aを押してからxを押します。
Piで見る
Raspberry Pi 5で動かすのとおりに、ボードを一度準備します。カード、Debug Probe、電源を入れる前につないだモニター、USBマウスです。そのうえで、showcase/から、コンテナではなく手元のマシンで実行します。
make pi-show PI_DEVICE=/dev/cu.usbmodemXXXX SYSTEM=showcase
これは、あなたの画面を含めてShowcaseをビルドし、プローブ経由でボードに送り、ボードの電源を入れ直すまで動かし続けます。React gallery、続いてBackをクリックすると、PiのHDMIモニターにあなたの画面が表示されます。
Rustから画面を動かす
画面は何が起きたかをアプリケーションに伝え、アプリケーションは何を表示するかを画面に伝えます。その方法はアクション、フィールド、スロット、グループの四つです。Screen Compilerのフィクスチャfixtures/visit-booking.tsxは、その四つすべてを使います。
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>
そのRustは、画面が書いた順に1から番号を付けて、それぞれに名前を付けます。
| TSXでは | Rustでは | アプリケーションは |
|---|---|---|
コールバックprop、onBook |
アクション、actions::BOOK |
ボタンが押されたことを知らされる |
idを持つコントロール、id="name" |
フィールド、fields::NAME |
入力された内容を読む |
テキストとして書いたprop、{props.phoneError} |
スロット、slots::PHONE_ERROR |
テキストを設定する |
何を表示するかを決めるprop、{props.phoneError && …} |
グループ、groups::PHONE_ERROR |
表示・非表示を切り替える |
アプリケーションは画面をNoWayOSのUIツリーに組み立て、スロットとグループを設定し、入力イベントを一つずつツリーに渡します。Bookをタップすると、それがBookのアクションとして返り、そこでアプリケーションがフィールドを読みます。
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,
_ => {}
}
これはScreen Compilerのリポジトリにあるテストcontract-check/tests/driving.rsのコードです。このテストは、変換した画面をNoWayOS自身のUIコアの上に組み立て、make ciのなかでBookをタップします。デバイスでは、イベントはNoWayOSの入力ブローカーから届き、ツリーはそのレンダラーが描きます。props.step === "review"のような比較も同じようにスイッチswitches::STEPになり、アプリケーションはset_caseでそれを設定します。
次に読むもの
Screen Compilerが受け付けるコンポーネント、props、スタイルプロパティはすべてReactから画面を作るにあります。既存のReactアプリケーションを移すなら、Reactから移植するとそのカバレッジレポートから始めてください。