NoWayOS
JA
GitHub
メニュー閉じる

ビルドする

はじめての画面

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から移植するとそのカバレッジレポートから始めてください。