Changing Shared LVGL Styles: Invalidate the Widgets That Use Them

After mutating a shared lv_style_t, notify LVGL that the style changed. Changing the style’s stored property and repainting one widget are different operations. A correct update must reach every relevant consumer and refresh the layout or extended drawing information affected by the changed properties.

Applicability: this article pins LVGL v9.3.0. The fixture attaches one shared mutable style to LV_PART_MAIN once per object, with style refreshing enabled. It has no active style transitions or competing local overrides. The update sequence is proposed; no simulator or hardware rendering test was executed.

A shared style mutation is followed by explicit notification and checks of active, retained and unrelated consumers; separate milestones distinguish resolved values, geometry and frame presentation.
Original hypothetical shared-style update fixture. Padding values are illustrative, with one main-part attachment per object and no executed LVGL test.

1. Inventory the consumers of the shared style

Identify the actual style object by its stable identity, not just a variable name copied into several files. List widgets that attach it, their parts and states, and whether they are on the active screen, another retained screen, or a separate display.

Separate shared mutation from per-widget customization. If only one warning indicator should change, mutating a style used by every status indicator is the wrong scope. Conversely, updating one object’s local property does not update its peers that still use the shared style.

Keep style lifetime valid through all uses. This article’s problem begins after lifetime and selector ownership are already correct: the intended shared object changes, but consumers do not consistently recompute or redraw. Do not solve that problem by recreating the style on every screen visit.

2. Distinguish stored values from notification

The tagged style implementation updates property storage and style-level metadata. It does not locate every object using that style and refresh each object’s derived state as part of a generic style-property assignment.

The v9.3.0 public header provides lv_obj_report_style_change(style) to notify consumers after a style is modified. Passing the changed style pointer gives the notification an explicit target. A null pointer requests a broader notification and should not be the default repair for one known change.

Contrast this with an object-local style setter: its implementation has an object to refresh immediately. Do not assume that a similarly named shared-style setter performs the same object-side work. Write the shared update as a deliberate mutation followed by notification.

3. Refresh more than the pixels

The v9.3.0 report path traverses display screen trees, finds matching attached style pointers, refreshes the relevant style cache, and requests a broad style refresh for the matching object. It recursively visits children as part of that search.

The refresh implementation considers layout, extended draw area, layer type, and inherited effects as well as invalidation. Therefore, merely requesting a repaint is not a general substitute for a style-change notification. A border, shadow, padding, transform, or text-related change can require more than coloring the same old rectangle again.

A property newly added to a shared style is an important test case. With object style caching enabled, the consumer’s knowledge that a property group is present also matters. Test both changing an existing property and introducing a previously absent one rather than accepting a color-only demonstration as complete coverage.

For more complex attachment patterns, such as the same style reused across several parts of one object, add dedicated coverage against the exact release. The minimal fixture here deliberately avoids treating those additional cache and attachment paths as already qualified.

4. Apply a coherent update through the UI owner

Suppose a hypothetical shared status style changes its background and increases main-part padding from 4 to 12 pixels. Construct the desired values first, then let the application’s existing UI owner apply the related property mutations and report the style change once.

Do not allow rendering or another UI operation to interleave halfway through a supposedly coherent appearance update. Use the project’s established LVGL ownership or synchronization contract. The notification API is not a substitute for that concurrency contract.

If immediate geometry inspection is needed afterward, complete the supported layout-update step before checking bounds. Otherwise observe the normal subsequent refresh. Keep “new properties stored,” “layout recomputed,” and “new frame presented” as separate milestones in the diagnostic trace.

Do not infer a missed notification merely because the physical panel has not changed immediately. Display scheduling and transfer completion introduce later boundaries. First establish the resolved property and geometry, then investigate rendering and presentation if necessary.

5. Handle temporarily disabled refreshing explicitly

lv_obj_enable_style_refresh controls a global style-refresh flag in the pinned source. Both the report and refresh entry points return early when refreshing is disabled. Re-enabling the flag does not itself replay a queue of the skipped notifications.

If application code uses this facility for a bounded batch, review every exit path. Re-enable refreshing, then issue the required notification for the completed changes. A failed update must not leave later unrelated UI work permanently running with refreshing disabled.

A simpler initial design keeps refreshing enabled and batches shared property mutations before one report. Introduce refresh suppression only when its performance benefit has been measured and the broader application can own the global interval safely.

6. Test visible, inactive, and unrelated consumers

Create hypothetical widgets A and B on the active screen using the shared main-part style, C on a retained inactive screen using it, and D using an independent style. Record their resolved properties before the change, apply the update, notify, and record them afterward.

A and B should resolve the new values under the fixture’s selectors. When C’s screen is activated, it should use the same current style state without requiring an accidental second mutation. D’s independent property values should remain unchanged; do not require that no unrelated invalidation event ever occurs.

For the padding change, inspect layout bounds as well as appearance. For a shadow or outline case, inspect the former and new drawing extent for stale pixels. For inherited text properties, include a child with no local value and a child that deliberately overrides the property.

Add a negative model case that mutates without notification to expose the missing stage. Its observed rendering is not a portable promise: some unrelated refresh may make a subset of the update visible. The contract must succeed without depending on that coincidence.

7. Separate notification defects from selector defects

If only one widget remains unchanged, check its local override, selector state, attached style pointer, and transition before broadening the notification. A correctly refreshed object can legitimately resolve a different winning rule.

The release artifact should contain the consumer inventory, mutation/notification sequence, refresh-enable state, and property/geometry/render observations. Accept shared updates when all intended consumers converge through the documented path, including retained screens, rather than when one forced redraw happens to look correct.

Similar Posts