PBToolboxAI v3 ← Site

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 #

Userobjectu_pbt_radialmenu
Item classn_pbt_radialmenu_item (of_item(id))
Used forOffering a handful of frequent commands where the hand already is
PrincipleYou 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 #

PropertyTypeDefaultRole
ii_max_sectorsinteger8How 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_stylestringfluentVisual style of the component (THEME_STYLE_* constants)
is_theme_modestringlightLight or dark variant (THEME_MODE_* constants)
il_theme_accentlong-1Accent colour of this component (-1 = the theme's accent)
is_tooltipstring""Plain tooltip shown when hovering the component
is_super_tooltip_titlestring""Title of the rich tooltip (takes precedence over is_tooltip)
is_super_tooltip_textstring""Text of the rich tooltip (rich markup accepted)
is_super_tooltip_imagestring""Image of the rich tooltip

Methods #

MethodRole
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 #

EventFired 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 #

PropertyTypeDefaultRole
is_textstring""Branch label. Keep it short: a sector is a slice, not a row — the hub spells out the rest
ib_enabledbooleantrueSet to false and the branch is greyed, its sector ignoring every click
ib_visiblebooleantrueSet 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_checkedbooleanfalseSet 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 #

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.

MembersRoleDetailed in
of_count · of_keys_at · of_hasWalk what the component holds3.2 Items
of_resetPut the component back to zero3.6 Resetting a component: of_reset()
of_register_shortcut · of_clear_shortcutsThe component's keyboard chords3.5 Keyboard shortcuts
of_is_created · of_is_ready · of_get_last_errorWhether it was born, whether it is ready, what failed3.7 Diagnostics
of_save_as_png · of_save_as_jpgExport the rendering as an image3.8 Exporting the rendering as an image
of_set_redrawGroup changes into a single repaint3.10 Best practices
of_preload_iconsIcons shown with no delayInstant display: of_icon
of_set_translationTranslate one of the component's labels5.2 Adapting a label: of_set_translation
of_focus_webviewGive the component the focus6.4 Keyboard and focus
of_print · of_print_to_pdfPrint, or write a PDF6.9 Printing
of_set_property · of_get_property · of_component_nameDriving a property by its name3.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.


← Component reference · Guide contents