만들기
첫 화면 만들기
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을 누르면, 첫 화면에서 마지막 화면, 곧 여러분의 화면으로 넘어갑니다. 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가 받는 모든 컴포넌트, prop, 스타일 속성은 React로 만드는 화면에 있습니다. 기존 React 애플리케이션을 옮겨 오려면 React에서 옮겨 오기와 그 커버리지 보고서부터 시작하십시오.