Layout and Widgets
layout.json lists the widgets on a Kamoji Mini App's screen, from back to front, and
can attach Lua code to gestures and events.
{
"widgets": [
{ "type": "circle", "cx": 120, "cy": 120, "r": 100, "fill": "none", "border": "#333333" },
{ "type": "label", "font": "large", "align": "center", "text": "{{ fmt('%.2f g', imu.lat_g or 0) }}" }
],
"on": {
"top_tap": "state.page = (state.page or 1) % 3 + 1"
}
}
The file can also be just the array of widgets, without "widgets" and
"on". Widgets are created in order, so later ones are drawn on top.
Values and bindings
Every property takes either a constant or an expression:
- A constant is applied once:
"y": 40,"color": "grey","text": "Hello". Numbers may also be written as strings ("y": "40"). - A string containing
{{ … }}is a binding: a Lua expression Kamoji re-evaluates 10 times a second, updating the widget only when the result changes. See Expressions and Lua.
Properties marked bindable below accept expressions; the others must be constants.
Position and size
The mini app's area is 240 × 240 px, with (0, 0) at the top left and the centre of the round screen at (120, 120).
- Without
align,xandyare the widget's top-left corner in pixels. - With
align, the widget is placed at that point of the screen andxandyare offsets from it."align": "center"with nox/ycentres the widget.
| Property | Bindable | Meaning |
|---|---|---|
align | No | center, top_left, top_mid, top_right, left_mid, right_mid, bottom_left, bottom_mid, bottom_right |
x, y | Yes | Position, or offset when aligned (px) |
w, h | No | Width and height (px), as numbers. What they do depends on the widget |
hidden | Yes | true hides the widget |
opa | Yes | Opacity: 0–1, or 0–255 |
color | Yes | The widget's main colour (below) |
Colours
Colours are "#RRGGBB", "#RGB", or one of these names:
| Name | Colour | Name | Colour |
|---|---|---|---|
white | #FFFFFF | black | #000000 |
red | #FF3B30 | green | #34C759 |
blue | #0A84FF | yellow | #FFD60A |
orange | #FF9F0A | cyan | #64D2FF |
magenta | #FF2D95 | grey / gray | #8E8E93 |
gold | #E8B23A |
An expression may also return a number, 0xRRGGBB. A colour Kamoji cannot
read is ignored, and the widget keeps its previous colour.
label
Text.
{ "type": "label", "font": "huge", "align": "center", "text": "{{ fmt('%02d:%02d', time.hour or 0, time.min or 0) }}" }
| Property | Bindable | Default | Meaning |
|---|---|---|---|
text | Yes | empty | The text. Numbers are written without trailing zeros (3, 2.5, 0.33); nil is empty |
font | No | medium | small, medium, large, huge, or a built-in font builtin:<name> |
color | Yes | white | Text colour |
text_align | No | center | left, center or right, within the label's width |
w | No | fits the text | Width; long text wraps onto more lines |
x, y, align, hidden, opa | As above |
The fonts are Barlow Condensed:
| Font | Size | Characters |
|---|---|---|
small | 18 px | Printable ASCII and ¢ £ ¥ © ® ° · × ÷ – — ‘ ’ “ ” • … € ™ − |
medium | 28 px | Same as small |
large | 44 px | Same as small |
huge | 96 px | Digits and space . , : ; - + % / ° ' " ( ) only: for clocks, counters and gauges |
A character the font does not have is not drawn, so accented letters, other scripts and emoji do not show.
Mini apps can also use some of the fonts of Kamoji's own screens, as
"font": "builtin:<name>":
| Font | Size | Used by |
|---|---|---|
builtin:exciteometer_value | Barlow Condensed Bold, 36 px | The Excite-O-Meter's numbers |
builtin:exciteometer_unit | Barlow Condensed, 19 px | The Excite-O-Meter's % |
builtin:status | Barlow Condensed, 24 px | Kamoji's status labels |
An unknown built-in name falls back to medium, with a warning in the log.
Your own fonts are coming later.
rect
A rectangle, filled or outlined.
{ "type": "rect", "x": 40, "y": 100, "w": 160, "h": 40, "radius": 8, "fill": "#1C1C1E", "border": "gold" }
| Property | Bindable | Default | Meaning |
|---|---|---|---|
x, y, w, h | x, y | Position and size. Always give w and h | |
fill | Yes | white | Fill colour, or "none" for an outline only |
radius | No | 0 | Corner radius (px) |
border | No | none | Border colour |
border_width | No | 2 | Border width (px), with border |
color | Yes | Same as fill |
circle
A circle, filled or outlined.
{ "type": "circle", "r": 9, "fill": "gold", "cx": "{{ 120 + (imu.lat_g or 0) * 100 }}", "cy": 120 }
| Property | Bindable | Default | Meaning |
|---|---|---|---|
cx, cy | Yes | Centre (px). With align, offsets like x / y (with "align": "center", from the centre of the screen) | |
r | No | 10 | Radius (px) |
fill | Yes | white | Fill colour, or "none" for an outline only |
border | No | none | Border colour |
border_width | No | 2 | Border width (px), with border |
color | Yes | Same as fill |
arc
A ring gauge, or part of one: a background track and a coloured indicator.
{ "type": "arc", "align": "center", "w": 220, "width": 10, "min": 0, "max": 59,
"start_angle": 270, "end_angle": 269, "track": "#1C1C1E", "color": "cyan",
"value": "{{ time.sec or 0 }}" }
| Property | Bindable | Default | Meaning |
|---|---|---|---|
value | Yes | The value; the indicator runs from the track's start to it | |
min, max | No | 0, 100 | Range of value (whole numbers) |
start_angle, end_angle | See below | 135, 45 | Angles of the track in degrees: 0 is 3 o'clock, clockwise. The default is a 270° gauge open at the bottom; 270 to 269 is a full ring starting at 12 o'clock |
w, h | No | 200, same as w | Diameter (px) |
width | No | 12 | Thickness of the ring (px) |
color | Yes | Indicator colour | |
track | No | #333333 | Track colour |
When start_angle or end_angle is an expression, it sets the
indicator's own angles instead of the track's, for an indicator that does not
start at the track's start (a needle segment, a centre-zero gauge). Use
value or angle expressions, not both.
bar
A straight progress bar. It is vertical when h is larger than w.
{ "type": "bar", "align": "center", "y": 60, "w": 160, "h": 12, "color": "green", "value": "{{ state.level }}" }
| Property | Bindable | Default | Meaning |
|---|---|---|---|
value | Yes | 0 | The value |
min, max | No | 0, 100 | Range of value (whole numbers) |
w, h | No | 160, 16 | Size (px) |
color | Yes | Indicator colour | |
track | No | #333333 | Background colour |
line
A line through two or more points: tick marks, clock hands, needles, simple graphs.
{ "type": "line", "width": 4, "color": "white", "points": [[120, 120], [120, 30]] }
| Property | Bindable | Default | Meaning |
|---|---|---|---|
points | Yes | [[x, y], …] in screen pixels. An expression returns a Lua table of the same shape, { {x, y}, … }. At most 64 points | |
width | No | 3 | Line width (px) |
rounded | No | true | Rounded line ends |
color | Yes | white | Line colour |
{ "type": "line", "width": 2, "color": "gold", "points": "{{ hand(time.sec * 6, 94) }}" }
where hand is a function in main.lua that returns
{ {120, 120}, {x, y} } (see the clock example).
chart
A live chart: each series' value is read every every_ms and added on the right, the older points
shift left. Good for "the last N seconds" of a sensor.
{ "type": "chart", "align": "center", "w": 180, "h": 110, "points": 60, "every_ms": 200,
"min": -100, "max": 100, "grid": [3, 0], "width": 3,
"series": [
{ "value": "{{ clamp((imu.lat_g or 0) * 100, -100, 100) }}", "color": "#E8B23A" },
{ "value": "{{ clamp((imu.long_g or 0) * 100, -100, 100) }}", "color": "#64D2FF" }
] }
| Property | Bindable | Default | Meaning |
|---|---|---|---|
series | [{ "value", "color" }, …]: one line (or set of bars) each. value is a number or an expression; a non-number (e.g. nil) leaves a gap | ||
every_ms | No | 1000 | How often the series are sampled (at least 50) |
points | No | 30 | Points kept on screen (2–240): points × every_ms is the time span shown |
min, max | No | 0, 100 | Value range of the vertical axis (whole numbers: scale small values up, e.g. g × 100) |
chart_type | No | line | line or bar |
grid | No | [3, 0] | Horizontal and vertical grid lines |
width | No | 3 | Line width (px) |
w, h | No | 200, 100 | Size (px) |
The chart has no background or border, so it sits directly on the black screen. Sampling runs only
while the mini app is on screen, under the same CPU budget as the other bindings; a hidden chart
(hidden) keeps sampling.
image
A picture. For now only images built into Kamoji's firmware can be shown,
named builtin:<name>; images from your mini app's own files are coming
later.
{ "type": "image", "src": "builtin:exciteometer_vignette", "x": 0, "y": 0, "opa": "{{ state.flash }}" }
| Property | Bindable | Default | Meaning |
|---|---|---|---|
src | No | builtin:<name>. An unknown name skips the widget, with a warning in the log | |
x, y, align, hidden, opa | As above. The image keeps its own size |
| Image | What it is |
|---|---|
builtin:exciteometer_vignette | A full-screen (240 × 240) red vignette, the Excite-O-Meter's "hard move" flash |
Gestures and events: on
on maps a gesture or event name to Lua statements that run when it
happens:
"on": {
"top_tap": "state.face = state.face % 5 + 1",
"top_hold": "state.n = 0",
"pinch": "state.n = state.n + 10",
"bump": "state.bumps = state.bumps + 1"
}
The names are the same as the Lua handlers without on_: top_tap,
top_hold, pinch, enter, exit, and the events bump, hard_brake,
hard_accel, lean. An event's magnitude is only passed to the Lua handler
functions, not to on code; see
Expressions and Lua.
Not available yet
Images from your own files, gif, canvas and free drawing are
designed but not built: an unknown widget type is skipped with a warning in
the log.