Radial menus and buttons
RadialMenu is a marking menu with nested rings and per-device tuning. SilkButton animates in and out and ignores input while it moves or is busy.
Updated pixi-silk 1.0.1View as Markdown
A radial menu puts its items in a ring around the pointer, so a press, a drag towards an item and a release pick it in one gesture. A button here is a widget that knows when it should not take a press.
Open a radial menu#
import { bindRadialTrigger, RadialMenu } from 'pixi-silk/ui/radial';
const menu = new RadialMenu({
items: [
{ id: 'select', label: 'SELECT', icon: drawPointer },
{ id: 'move', label: 'MOVE', icon: drawMove, children: [{ id: 'axis', label: 'Axis' }, { id: 'free', label: 'Free' }] },
{ id: 'delete', label: 'DELETE', icon: drawTrash, disabled: true },
],
sweep: Math.PI * 2, // Math.PI for a half ring
bounds: { x: 0, y: 0, width: app.screen.width, height: app.screen.height },
onSelect: (item, path) => console.log(path.map((i) => i.id).join(' > ')),
});
app.stage.addChild(menu);
bindRadialTrigger(toolButton, menu); // or menu.openAt(x, y, event)
A press on toolButton opens the menu at the pointer. onSelect gets the item and the path to it
through the rings, so a pick in the second ring logs move > axis.
The menu picks by angle. Past a small dead zone the whole wedge counts, out to the falloff distance, so a fast flick lands on its item even when the pointer overshoots the ring. The current item holds until the pointer is clearly past its edge, so a shaky finger on a boundary does not flicker between two items.
Items with children open the next ring around themselves, to any depth. A full ring slides to
stay inside bounds, and a partial ring turns to face the inside instead.
| Option | Default | What it does |
|---|---|---|
innerRadius, outerRadius | 40, 100 | The first ring |
sweep | full turn | Angle of the first ring, Math.PI for a half |
direction | up | Where the ring faces, in radians |
closeOnLeave | false | Close once the pointer stays outside the falloff area. By default the menu stays |
closeOnSelect | true | Close after a pick |
selectParents | false | Items with children can be picked too |
profiles | per pointer kind | Dead zone, falloff, scale, tap time and hysteresis for mouse, pen and touch |
hub | true | Draw the centre disc |
Touch gets bigger targets and more forgiveness than a mouse. Override any of it with profiles.
Icons are a string, a texture, a container or a function that draws with SilkGraphics in white. The menu tints them, so a hover colour costs no redraw.
Make a button that waits#
import { SilkButton } from 'pixi-silk/ui/button';
const play = new SilkButton({
shape: 'circle',
height: 76,
icon: drawPlay,
holdMs: 450,
onPress: () => console.log('play'),
onHold: () => console.log('play on repeat'),
});
const share = new SilkButton({ label: 'Share' });
await share.hide(); // resolves when the animation ends
play.state.busy.set(true); // spinner, input off
play.state.active.subscribe((on) => console.log('accepts input', on));
A button accepts input only when it is shown, settled, enabled and not busy. That is what keeps a
save button from being pressed twice while it saves. show() and hide() return promises that resolve false
if the animation was reversed half way. A press that slides a little off the button still counts
on release, within hitPadding (6 by default).
ButtonDock from pixi-silk/ui/dock lines buttons up and closes the gap when one hides. Each
button’s space follows its animated appearance, so the reflow and the fade move together.
State in signals#
Every widget keeps its state in state: small signals you can read, write and subscribe to.
pixi-silk/signal has no Pixi code in it. A signal is a value with subscribe, with no
dependency tracking, so it plugs into React, MobX or plain callbacks.
import { batch, computed, signal } from 'pixi-silk/signal';
const volume = signal(1);
const label = computed([volume], () => `volume ${volume.value}`);
volume.subscribe((now, before) => console.log(before, '->', now));
batch(() => {
volume.set(2);
volume.set(3);
}); // one notification
computed takes its sources as a list, so it recomputes only when one of them changes. Widgets
animate on Ticker.shared and leave the ticker once nothing moves. Pass ticker to use your
application’s ticker, or null to step them yourself with tick(dt).
API reference: RadialMenu, bindRadialTrigger, SilkButton, ButtonDock