Laying out a panel
There is nothing to do. Controls line up in the order you make them, on a plate, with the keyboard along the bottom. On a narrow screen the line wraps evenly (six dials are three and three, never five and one), on a touch screen everything is a little bigger, and when the picture changes size it is all laid out again.
This page is for when you want to say more: what goes with what, how many to a line, how big, and what happens on a phone.
A label is a heading
A label made just before some controls is the title of their plate, and they are kept together: a plate moves to the next line whole, never leaving its heading behind. Plates with titles fill the width. They stand side by side, as many to a line as fit, and row() says where a line should end when one has to.
This is a whole synth panel, and it needs nothing else:
const lead = makeSynth("lead")
label("oscillator")
select("wave", lead, "wave")
dial("width", lead, "width")
row()
label("unison")
dial("voices", lead, "voices")
dial("detune", lead, "detune")
label("filter")
dial("cutoff", lead, "cutoff")
dial("resonance", lead, "resonance")
scope(lead)
keyboard(lead)Groups
group says the same thing out loud, and takes options. In JavaScript what the function makes is in the group. In Python it is what is made inside with.
group("filter", () => {
dial("cutoff", lead, "cutoff")
dial("resonance", lead, "resonance")
})Or in a straight line, with no brackets to match and nothing to indent: group("filter") and then its controls, until the next group, row() or page.
The plates of a line share its width and are as tall as each other, and every line is as wide as the others, so the panel looks made and not poured. This is a whole synth in seven groups:
A group takes these options:
| Option | What it does |
|---|---|
colour | The colour of its title and of every control in it |
columns | columns: 3 lays its controls out three to a line, as a grid |
gap | The room between its controls |
align | "start", "center" or "end". Left out, its controls are spread along the line |
dividers | true draws a line between the groups inside it |
plate | false leaves the plate out |
wrap | false keeps its controls on one line, whatever the width |
at | "left", "right", "top" or "bottom": a strip along that edge. See Along an edge |
Rows, columns and grids
Inside a group, controls stand in a line. To arrange them another way, put these in it. They can go inside each other.
| JavaScript | Python | What it does |
|---|---|---|
row(() => { … }) | with row(): | In a line. row() with nothing in it starts a new line |
column(() => { … }) | with column(): | One under another |
grid(3, () => { … }) | with grid(3): | Three to a line, in columns that line up; fewer where three will not fit |
spacer() | spacer() | All the room that is left over, so what follows is pushed to the far end. spacer(20) is 20 points |
Three options go on any control:
| Option | What it does |
|---|---|
size | "small", "large", or so many times the usual size: 1.5. A dial, a slider and a meter also take a size in points (a number over 4) |
span | In a grid, span: 2 covers two columns |
share | The control takes the room its line has left over. Two things with shares of 2 and 1 split the line two to one. A group takes it too |
group("filter", { colour: "deepskyblue" }, () => {
column(() => {
select("kind", lead, "filter")
toggle("mute")
})
grid(2, () => {
dial("cutoff", lead, "cutoff", { size: "large", span: 2 })
dial("resonance", lead, "resonance", { size: "small" })
dial("chorus", lead, "chorus", { size: "small" })
})
})Size and fit
By itself, a panel fits whatever it is on. On a phone held upright its plates are one to a line and are moved up and down with a finger, under a keyboard that stays put. In a tablet's split view they are two to a line, and across a whole tablet three or four, each line reaching both edges. On a touch screen everything is a little bigger, and nothing is ever drawn too small for a finger.
Three lines change that: a scale, fitting the width, and a panel of a set size.
Bigger or smaller
layout({ scale: 1.3 })This draws everything that much bigger, and it works with everything below. Call layout before the controls it is for.
Fit the width
wrap: false keeps every row exactly as you wrote it (each row() is one row, whatever the screen) and draws the whole panel smaller until the widest row fits the width. There is one scale for the whole panel, set by that widest row, so a knob is the same size on every row.
layout({ wrap: false })
label("oscillator")
select("wave", lead, "wave")
dial("width", lead, "width")
dial("pwm", lead, "pwm")
dial("sub", lead, "sub")
row()
label("unison")
dial("voices", lead, "voices")
dial("detune", lead, "detune")
label("filter")
dial("cutoff", lead, "cutoff")
dial("resonance", lead, "resonance")
row()
label("shape")
slider("attack", lead, "attack")
slider("decay", lead, "decay")
slider("sustain", lead, "sustain")
slider("release", lead, "release")
scope(lead)
keyboard(lead)layout({ fit: "width" }) is the same, and also draws the panel bigger where there is room to spare, so that the widest row reaches both edges: no bigger than maxScale (1.6 unless you say), and no bigger than still fits the height. In Python: layout(fit="width", max_scale=2).
Under a finger the panel is not drawn smaller than eight tenths of its size, which is as small as is comfortable to touch (with a mouse, half). If the rows are still too wide they are left that size and moved from side to side. You choose the floor: layout({ wrap: false, minScale: 1 }) never shrinks at all, and minScale: 0 shrinks as far as it takes (min_scale in Python).
A panel of a set size
layout({ size: [900, 640] })This lays the panel out once, as if every screen were 900 by 640, and then makes the whole of it bigger or smaller to fit the screen it is on, keeping its shape and standing in the middle. Nothing moves about from one screen to the next: only the size changes.
A width alone, layout({ size: 900 }), fills the width, and what does not fit is moved up and down.
A bigger knob is your choice, and it is kept
size on any control makes it that much bigger or smaller than its neighbours, on every screen and at every scale. Its label and its value still line up with theirs.
layout({ wrap: false })
group("filter", () => {
rotary("kind", lead, "filter")
dial("cutoff", lead, "cutoff", { size: "large" })
dial("resonance", lead, "resonance")
dial("sweep", lead, "sweep", { size: "small" })
})
group("mix", () => {
dial("volume", lead, "volume", { size: 1.5 })
dial("pan", lead, "pan")
})Controlling wrapping
Say nothing and the panel decides. Say something and it does as it is told.
| JavaScript | Python | What it does |
|---|---|---|
layout({ wrap: false }) | layout(wrap=False) | Never wrap. Every row is the row you wrote, drawn smaller to fit the width |
layout({ wrap: true }) | layout(wrap=True) | Every row() starts a new line, and a row is split only when it does not fit |
layout({ columns: 3 }) | layout(columns=3) | Three plates to a line, always |
layout({ columns: { phone: 1, tablet: 2, wide: 3 } }) | layout(columns={"phone": 1, "tablet": 2, "wide": 3}) | How many at each width. phone is any width, tablet 600 points and up, wide 900 and up |
group("shape", { wrap: false }, …) | with group("shape", wrap=False): | This group's controls stay on one line, whatever the width |
row({ wrap: false }, () => { … }) | with row(wrap=False): | The groups made inside stay side by side |
row() | row() | A new line starts here. Inside a group too |
wrap() | wrap() | If this line has to be split, split it here. Nothing happens while it fits |
When a row that may not wrap is too wide, the whole panel is drawn smaller, not that row alone: one scale, as above.
layout({ columns: { phone: 1, tablet: 2, wide: 3 } })group("shape", () => {
slider("attack", lead, "attack")
slider("decay", lead, "decay")
wrap() // split here, if it must be split: never between sustain and release
row({ wrap: false }, () => {
slider("sustain", lead, "sustain")
slider("release", lead, "release")
})
})Pages
When there is too much for one screen, give it pages. Each has a tab. The keyboard, and what is made before the first page and after the last, stay on every page.
page("osc")
group("oscillator")
select("wave", lead, "wave")
dial("width", lead, "width")
page("filter")
group("filter")
dial("cutoff", lead, "cutoff")
dial("resonance", lead, "resonance")
page() // the pages end here
scope(lead)
keyboard(lead)A page can hold its controls in a function too, and show() turns to it from your code:
const fx = page("fx", () => {
dial("echo", lead, "echo")
dial("reverb", lead, "reverb")
})
button("effects", () => { fx.show() })When there is more than fits
Controls are drawn a little smaller before a line has to wrap, but never tiny. When there is more than fits, the plates can be moved: with a finger (they carry on a little when it lets go), the mouse wheel, a trackpad or the thin bar beside them. The keyboard and the scope above it stay where they are.
- A drag that starts on a dial turns the dial.
- A drag that starts on a plate's title, on words or between the plates moves them, passing over dials without turning any.
- On a slider that lies across, a finger going along it moves the slider, and one going up or down moves the plates.
The keyboard is the instrument, and keeps its height. When the plates, the scope and the keyboard do not all fit, the scope is made shorter (never too short to read a wave), and if that is not enough it is the plates that are moved, under a scope and a keyboard that stay put. keyboard(lead, { height: 200 }) is kept to, whatever the room (height=200).
On a phone
A phone held upright has one plate to a line, moved up and down with a finger under the keyboard. On a phone on its side, where there is hardly any height, the keyboard gives up some of its own and the scope goes away.
A panel of a set size made for a tablet is small on a phone. One more line gives the phone its own:
layout(layout().width < 600 ? { wrap: false } : { size: [900, 640] })layout() with nothing gives back what the layout came to: its width, height and scale. onResize is called with the same three when the picture changes size, so a scene can take everything away and make another arrangement (on_resize in Python). In Python the page tells these with a frame: in a scene's very first lines the scale is still 1.
Along an edge, and the room that is left
layout({ at: "bottom" }) puts the plates at the bottom, and "left" or "right" in a column down that side. One group can be a strip along an edge, and room() is the rectangle no control is on, for the scene to draw in:
group("moves", { at: "left" }, () => {
dial("speed", 0, 10, 4)
button("jump")
spacer()
toggle("trail")
})
group({ at: "bottom" }, () => {
button("left")
button("right")
spacer()
button("fire", { size: "large" })
})
function draw() {
const area = room() // { x, y, width, height }
rect(area.x, area.y, area.width, area.height, "steelblue")
}In Python the page works the rectangle out and tells Python with a frame, so in the scene's very first lines room() is still the whole picture.
Exactly where you say
x and y on a control or a group place its top left corner, and control.place(x, y) moves it. With a panel of a set size those numbers are in that panel, which is scaled to fit the picture, so a front panel drawn once is right on every screen:
layout({ size: [800, 480] })
dial("cutoff", lead, "cutoff", { x: 360, y: 30, size: "large" })
group("osc", { x: 20, y: 20, width: 300 }, () => {
select("wave", lead, "wave")
dial("sub", lead, "sub")
})
keyboard(lead, { x: 20, y: 340, width: 760, height: 120 })layout({ image: "panel.png" }) puts a picture of your own behind the controls.
The look
layout also takes these, and theme is one call for the colours.
| Option | What it does |
|---|---|
compact | true: less room around everything |
dividers | true: a line between plates |
plates | false: no plates, for a picture of your own behind |
backdrop | true, false or a colour: a panel behind everything |
align | "center": plates keep their own widths |
margin | The space round the plates |
Related
- Every control
- Layout and touch: colours, hands and keys
- Tying a control to a synth