LVGL Button Matrices: Preserve Action Identity When Labels and Layouts Change

Map each button-matrix index to a stable application action, and regenerate that mapping whenever the displayed button order changes. Do not dispatch by translated text, raw string-array position or remembered numeric index. Labels, row separators, control flags and action IDs must describe the same matrix generation.

Applicability: this procedure pins LVGL v9.3.0 and a command matrix with serialized UI updates. The proposed fixture uses release-triggered, nonrepeating commands and replaces maps only after input has become quiescent. It does not implement a text editor or focus-navigation system. All examples and interaction tests are hypothetical and unexecuted.

Two generations reorder three actions; the disabled Stop action moves from index one to two, demonstrating why controls must be regenerated with the action table.
Original hypothetical map-generation example. Index and disabled-state relationships follow the stated maps; no input execution is claimed.

1. Distinguish map entries from buttons

The button-matrix guide describes a string map containing button labels, newline entries and a terminator. Newlines and the terminating empty string or null entry do not count as buttons. The visible controls are drawn within one widget; they are not separate child button objects with independent user-data fields.

For a hypothetical matrix, use map entries “Start,” “Stop,” newline, “Details,” and an empty terminator. The text-array indexes are 0, 1, 2, 3 and 4, but the button indexes are only 0, 1 and 2. Details is button 2, not button 3.

Map entry Text index Button index Stable action
Start 0 0 ACTION_START
Stop 1 1 ACTION_STOP
Newline 2 None None
Details 3 2 ACTION_DETAILS
Terminator 4 None None

Generate the action array from the same structured description as the text map. Do not maintain a second hand-counted list after inserting a row break. Assert that the number of action entries and control entries equals the count of actual buttons.

2. Resolve identity at the event boundary

For widget-generated LV_EVENT_VALUE_CHANGED, the tagged implementation passes a pointer to a local uint32_t button index. Copy the value during the callback; do not queue the parameter pointer. Validate the index against the current generation’s action table before looking up the action.

If the application uses lv_buttonmatrix_get_selected_button instead, handle LV_BUTTONMATRIX_BUTTON_NONE explicitly. Neither a valid selected index nor a matching caption alone authorizes the corresponding operation. Check the current application state and command availability before issuing a request.

Queue a semantic action and, where relevant, a matrix or view generation, rather than an index to resolve later. Otherwise an index captured before a language/layout rebuild can mean another action when the command worker consumes it. Decide whether a retired generation cancels pending commands and test that decision.

3. Keep control flags attached to actions

The control-map API requires one entry per button, excluding newlines, and copies those entries. The text-map setter retains a reference to the supplied map. Therefore, keep text strings and their pointer array alive while referenced, while a temporary control array can be released after the copy completes.

Hidden buttons retain their layout space and button identity. Removing a button from the map is a different operation that changes later indexes. A disabled command should retain the correct action mapping even though ordinary interaction cannot activate it.

There is an important exact-version replacement detail: the allocation helper returns early when the new button count equals the old count, preserving the existing indexed controls. Reordering three labels does not automatically move disabled, hidden or checked state to the matching semantic action.

For example, suppose Stop is disabled at index 1. Reorder to “Details,” newline, “Start,” “Stop.” The new indexes are Details 0, Start 1 and Stop 2. Reusing the old controls disables Start instead. Regenerate the entire control map from action policy after installing the new text map.

4. Treat map replacement as one application transaction

  1. Build a candidate generation containing text entries, button-to-action entries and controls derived from the same action records.
  2. Validate terminators, button counts, unique action identities where required and all referenced string lifetimes.
  3. Wait for the current interaction to finish or cancel it through the application’s reviewed input policy. Block command dispatch during replacement.
  4. Install the text map, install the corresponding control map and publish the matching action table under the same serialized UI ownership.
  5. Clear stale selection or restore it by finding the prior semantic action’s new index, provided that action is still eligible.
  6. Retire old storage only after its readers are finished, then reopen command dispatch.

This is an application transaction, not an atomic multi-array LVGL setter. A callback that sees new labels with old actions is a publication defect even when every individual setter call is valid. Keep the generation guard in the command handler so accidental intermediate notifications cannot issue work.

5. Fix event timing for the command fixture

The tagged source sends value-changed notifications from press, release or long-press-repeat paths depending on control flags. For this fixture, set CLICK_TRIG and NO_REPEAT for command buttons and leave popover/checkable behavior outside scope. Verify one action on the intended completed interaction instead of assuming every value-changed event represents a fresh click.

Test the chosen policy through real pointer and keypad/encoder integration if those inputs are supported. Synthetic value-changed events test mapping only; they do not prove hit testing, release cancellation or repeat suppression. A hidden or disabled appearance also does not replace application authorization checks.

6. Build the identity regression set

Begin with the three-action map, then translate captions without changing order. Move the row break, reorder all actions, hide Details, disable Stop and finally remove Details entirely. For each generation, enumerate button index, displayed label, action ID and control bits, and compare them with the generated source description.

Activate every eligible button and record the semantic request, not merely the callback count. Attempt hidden and disabled positions, hold a command through the repeat interval, and request a rebuild while input is held. The latter must follow the declared wait-or-cancel policy without letting a release invoke the replacement action at that index.

Save the generated mapping and event traces with the LVGL tag. Acceptance means that presentation changes preserve command identity, unavailable commands remain unavailable and no event is interpreted through a mismatched map generation.

Similar Posts