UI widgets

Current EMWaver UI scripts import components from emw-ui, import JSX and render from emw-jsx, then render JSX. The JSX is converted to the same serializable UI tree used by the native renderers.

Imports and render

import { JSX, render } from "emw-jsx";
import {
  Button,
  Card,
  Column,
  Divider,
  Grid,
  LogViewer,
  Modal,
  Picker,
  Plot,
  Progress,
  Row,
  Scroll,
  Slider,
  Spacer,
  Text,
  TextEditor,
  TextField,
  Tile,
  Toggle,
} from "emw-ui";

function App() {
  return <Text>Hello from EMWaver</Text>;
}

render(<App />);

Layout

<Column padding={16} spacing={12}>
  <Text>Stacked vertically</Text>
  <Row spacing={8}>
    <Button>Left</Button>
    <Button>Right</Button>
  </Row>
  <Grid columns={3} spacing={8}>
    <Tile title="A" value="1" />
    <Tile title="B" value="2" />
    <Tile title="C" value="3" />
  </Grid>
  <Divider />
  <Spacer minLength={20} />
</Column>

<Scroll padding={16} spacing={14}>
  <Text>Scrollable page content</Text>
</Scroll>

Card

<Card title="GPIO Control" subtitle="Pin A0">
  <Button onTap={toggle}>Toggle</Button>
</Card>

Text

<Text>Hello</Text>
<Text font="title2" fontWeight="semibold">Sampler</Text>
<Text font="caption" foregroundColor="#6B7280">
  Last reading: {String(reading)}
</Text>

Button

<Button id="capture.start" onTap={startCapture}>Start capture</Button>
<Button buttonStyle="borderedProminent" controlSize="large" onTap={save}>
  Save
</Button>

Tile

<Tile
  title="Temperature"
  value={temperatureText}
  monospaceValue={true}
  subtitle="Last reading"
  onTap={refresh}
/>

Slider

<Slider
  id="pwm.duty"
  value={duty}
  min={0}
  max={4095}
  step={1}
  label="Duty cycle"
  onChange={(value) => { duty = Number(value); rerender(); }}
  onSubmit={(value) => { writeDuty(Number(value)); }}
/>

TextField

<TextField
  value={command}
  placeholder="Enter command..."
  onChange={(value) => { command = String(value || ""); }}
  onSubmit={(value) => { sendCommand(String(value || "")); }}
/>

<TextField value={apiKey} secure={true} placeholder="API key" />

TextEditor

<TextEditor
  value={hexData}
  placeholder="Data bytes..."
  minHeight={120}
  onChange={(value) => { hexData = String(value || ""); rerender(); }}
/>

Picker

<Picker
  id="gpio.pin"
  selected={selectedPin}
  options={[
    { label: "A0", value: "0" },
    { label: "A1", value: "1" },
    { label: "GPIO4", value: "4" },
  ]}
  style="menu"
  onChange={(value) => { selectedPin = String(value); rerender(); }}
/>

<Picker
  style="segmented"
  selected={mode}
  options={[{ label: "RX", value: "rx" }, { label: "TX", value: "tx" }]}
/>

Toggle

<Toggle
  label="Enable output"
  value={enabled}
  onChange={(value) => { enabled = Boolean(value); rerender(); }}
/>

Plot

Plot can render inline data or a named/buffer-backed source. The sampler UI uses source="samplerBits" with viewport callbacks for pan/zoom.

<Plot
  height={240}
  dataX={[0, 1, 2, 3, 4]}
  dataY={[0, 1, 0, 1, 0]}
/>

<Plot
  height={240}
  source="samplerBits"
  bins={400}
  xMin={xMin}
  xMax={xMax}
  yMin={-10}
  yMax={265}
  errorText={chartErr}
  onViewportChange={(rangeValue) => {
    const range = parseViewportRange(rangeValue);
    if (range) scheduleViewport(range.min, range.max);
  }}
/>

const bufId = buffer(capturedBytes);
<Plot height={240} source={{ kind: "buffer", id: bufId }} />

LogViewer

<LogViewer text={logLines.join("
")} minHeight={220} />

Progress

<Progress value={42} total={100} label="Flashing..." />
<Progress label="Scanning..." />

Modal

<Modal
  open={showModal}
  title="Confirm"
  subtitle="Are you sure?"
  onClose={() => { showModal = false; rerender(); }}
>
  <Button onTap={confirm}>OK</Button>
</Modal>

Dynamic children and nulls

Components ignore null, undefined, and false children, so conditional UI can be written naturally.

<Column spacing={10}>
  <Text font="title2">Radio</Text>
  {statusText ? <Text font="caption">{statusText}</Text> : null}
  {items.map((item) => <Tile title={item.name} value={item.value} />)}
</Column>

Common style props

PropTypeNotes
paddingnumber or edge objectUse 16 or { top, bottom, leading, trailing }.
spacingnumberGap between children for layout nodes.
width, heightnumberFixed size.
minWidth, maxWidth, minHeight, maxHeightnumberSize constraints.
cornerRadiusnumberRounded corners.
backgroundColor, foregroundColorstringNative/platform color string.
fillsWidthbooleanStretch to available width.
alignmentstringCommon values: leading, center, trailing.

Event handlers

PropUsed byPayload
onTapButton, TileNo payload.
onChangePicker, Slider, Toggle, TextField, TextEditorNew value.
onSubmitTextField, SliderCommitted value.
onViewportChangePlotViewport range with min and max.
onSelectRangePlotSelected range.
onCloseModalNo payload.