radialmenu — u_pbt_radialmenu #
← Component reference · Guide contents
Radial context menu: the commands in a ring around the cursor, picked by direction rather than read down a list.
▶ See it live — Demo application, Radial menu tile: the preview, the code behind it and this page, side by side.
In brief #
| Userobject | u_pbt_radialmenu |
| Item class | n_pbt_radialmenu_item (of_item(id)) |
| Used for | Offering a handful of frequent commands where the hand already is |
| Principle | You describe the branches; the wheel, the shape and the navigation are ours |
Quick start #
// A wheel built for whatever is under the cursor
uo_wheel.of_add_item(/*keys*/ "cut", /*text*/ "Cut")
uo_wheel.of_add_item(/*keys*/ "copy", /*text*/ "Copy")
uo_wheel.of_add_item(/*keys*/ "paste", /*text*/ "Paste")
uo_wheel.of_add_item(/*keys*/ "delete", /*text*/ "Delete")
// It opens when the right button is released: see ue_rclicked
Open, then choose #
The wheel opens when the right button is released, and a second click is what chooses. That is not an implementation detail: were it to open on the press, the release of that very click would immediately pick whatever sector had landed under the cursor, and the user would fire a command without ever seeing it.
That is why of_show() is called from the right-click event of the control the user made the gesture on (a statictext, a button, a grid…): by then the button is already up, and the next click really is the one that chooses. The radial menu itself has no surface — so no mouse event of its own.
The component itself is invisible: it takes no room in the window. Drop it anywhere, give it zero width and height — it only exists while the wheel is up.
The hub in the middle spells out the pointed branch in full, which is what lets a sector carry a short label without lying about what it does. Clicking the hub closes the wheel; inside a sub-wheel, it climbs one level back.
// It opens when the right button is released: see ue_rclicked
uo_wheel.of_show()
Sub-wheels #
An address hangs branches under another one: export/pdf. Choosing the parent branch chooses nothing: the wheel is replaced by a wheel of its children, and the hub becomes the way back.
Why replace rather than add a second ring? Because an outer ring would halve the sectors at every level. Eight branches is already the readable maximum; there is no room to show two levels at once.
The ue_item_selected event reports the full path (export/pdf), not the bare leaf id. Two sub-wheels are therefore free to name their branches alike.
uo_wheel.of_add_item(/*keys*/ "export", /*text*/ "Export")
uo_wheel.of_add_item(/*keys*/ "export/pdf", /*text*/ "PDF")
uo_wheel.of_add_item(/*keys*/ "export/csv", /*text*/ "CSV")
When there are too many branches #
A wheel is read by direction, and past eight sectors the slices stop being distinguishable. ii_max_sectors sets that ceiling (3 to 12, 8 by default).
Branches beyond it are not lost: the last place in the ring becomes a branch holding all of them, which opens as a sub-wheel. A menu that dropped its tail would be a menu lying about what it offers.
Tightening the ring is often a gain: four wide branches are quicker to aim at than eight narrow ones.
Properties #
| Property | Type | Default | Role |
|---|---|---|---|
ii_max_sectors | integer | 8 | How many branches one ring may carry (3 to 12). Whatever exceeds it moves under a last branch that opens as a sub-wheel |
is_theme_style | string | fluent | Visual style of the component (THEME_STYLE_* constants) |
is_theme_mode | string | light | Light or dark variant (THEME_MODE_* constants) |
il_theme_accent | long | -1 | Accent colour of this component (-1 = the theme's accent) |
is_tooltip | string | "" | Plain tooltip shown when hovering the component |
is_super_tooltip_title | string | "" | Title of the rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the rich tooltip |
Methods #
| Method | Role |
|---|---|
of_add_item (string as_keys, string as_text) | Adds a branch to the wheel; the id comes back when it is chosen. Returns 0 once applied, -2 when the component is not created |
of_add_item (string as_keys, string as_text, string as_image, boolean ab_checked) | Same, with the icon and the mark — the icon third, as everywhere else in the library. A branch is greyed through its handle: of_item(keys).ib_enabled = false. Returns 0 once applied, -2 when the component is not created |
of_clear ( ) | Empties the wheel. Returns 0 once applied, -2 when the component is not created |
of_show ( ) | Opens the wheel centred on the cursor. Call it from the right-click of the control that receives the gesture |
of_show (long al_x, long al_y) | Same, centred on a screen position in pixels |
of_item (string as_keys) | Returns a branch handle so you can change it later (label, enabled, checked) |
of_reset ( ) | Empties the wheel and puts every property back to its default. Returns 0 once applied, -2 when the component is not created |
Events #
| Event | Fired when |
|---|---|
ue_item_selected (string as_keys) | The user chose a branch. as_keys is the full path (export/pdf), not the bare leaf id |
ue_dismissed ( ) | The wheel closed without any branch being chosen: a click on the hub, a click outside, or the Escape key |
The wheel's window is round: the corners let clicks through to the application behind instead of swallowing them in an invisible square.
Item properties #
| Property | Type | Default | Role |
|---|---|---|---|
is_text | string | "" | Branch label. Keep it short: a sector is a slice, not a row — the hub spells out the rest |
ib_enabled | boolean | true | Set to false and the branch is greyed, its sector ignoring every click |
ib_visible | boolean | true | Set to false and the branch leaves the wheel — sub-wheel included — without being removed; the sectors close up, and it comes back as it was |
ib_checked | boolean | false | Set to true and a dot marks the branch as being on |
Examples #
One wheel per context #
// A wheel built for whatever is under the cursor
uo_wheel.of_clear()
uo_wheel.of_add_item(/*keys*/ "bold", /*text*/ "Bold", /*image*/ "mono:img\packimages.dll:svg/samples/bold", /*checked*/ true)
uo_wheel.of_add_item(/*keys*/ "find", /*text*/ "Find", /*image*/ "mono:img\packimages.dll:svg/samples/find", /*checked*/ false)
Acting on the chosen path #
// ue_item_selected event of the radial menu
// as_keys carries the full path, e.g. "export/pdf"
choose case as_keys
case "export/pdf"
of_exporter_pdf()
case "delete"
of_supprimer()
end choose
Mark, grey out, tighten #
// Mark a branch as being on
uo_wheel.of_item(/*key*/ "bold").ib_checked = true
// Grey out the one that makes no sense here
uo_wheel.of_item(/*key*/ "paste").ib_enabled = false
uo_wheel.ii_max_sectors = 4
Good practice #
- Open on the control's RELEASED right-click, never on the press. That is what separates opening from choosing, and the user needs both.
- Short labels. One or two words. The hub is there for the whole text, the sector is there for the direction.
- Four to six branches beat eight. A wheel is remembered by position; the fewer positions, the faster they are learnt.
- Put the most frequent commands at the top and the bottom. Those are the two directions the hand reaches without thinking.
- Keep the order stable from one opening to the next: the whole point of a wheel is that the gesture ends up preceding the reading.
- A radial menu does not replace a list menu. Twenty rare commands read better in a list; keep the wheel for the handful used constantly.
Inherited from the common base #
These members exist on every visual component — they are not specific to this one. They are detailed once, in the transverse chapters; this table only says where to read them.
| Members | Role | Detailed in |
|---|---|---|
of_count · of_keys_at · of_has | Walk what the component holds | 3.2 Items |
of_reset | Put the component back to zero | 3.6 Resetting a component: of_reset() |
of_register_shortcut · of_clear_shortcuts | The component's keyboard chords | 3.5 Keyboard shortcuts |
of_is_created · of_is_ready · of_get_last_error | Whether it was born, whether it is ready, what failed | 3.7 Diagnostics |
of_save_as_png · of_save_as_jpg | Export the rendering as an image | 3.8 Exporting the rendering as an image |
of_set_redraw | Group changes into a single repaint | 3.10 Best practices |
of_preload_icons | Icons shown with no delay | Instant display: of_icon |
of_set_translation | Translate one of the component's labels | 5.2 Adapting a label: of_set_translation |
of_focus_webview | Give the component the focus | 6.4 Keyboard and focus |
of_print · of_print_to_pdf | Print, or write a PDF | 6.9 Printing |
of_set_property · of_get_property · of_component_name | Driving a property by its name | 3.1 The property engine |
Two helpers are not inherited: of_icon and of_escape_markup live on n_pbt_utils. Declare one — n_pbt_utils lnv_utils, nothing to create — and call them on it.