Your screen mock-up has buttons, but pressing them is where product behavior starts to become real. Imagine a small audio controller with Play, Settings and Back controls. Before connecting the amplifier, the team can already answer a useful question: does each action produce the response the user expects, and does the interface recover when the screen changes?
We worked through that question in a browser-based LVGL simulator running MicroPython against the LVGL 9.0 API, with no display hardware attached. Four small experiments covered shared button callbacks, resizing a widget, deleting it, and handling a group of buttons from their parent. The "turn on LED" and "play music" actions in these tests only printed console messages, so what we demonstrated is event routing, not physical output.
Connect the button to an action you can observe
An event is a notification that something happened to a widget. A callback is the function that receives it. In the first test, three buttons shared one callback, which checked which button the click came from. That lets you try several actions without maintaining three nearly identical functions.
A minimal version of the routing looks like this, assuming the three buttons already exist:
ACTIONS = {btn1: "request LED on", btn2: "request music", btn3: "request home screen"}
def on_click(e):
print(ACTIONS.get(e.get_target_obj(), "unknown button"))
for btn in ACTIONS:
btn.add_event_cb(on_click, lv.EVENT.CLICKED, None)
In our run each click printed the matching intention. That distinction matters when a designer reviews the demo: a button labeled "music" and a log saying "play music" are enough to discuss navigation, but they do not show an audio subsystem working.
Decide when an action should count
A press, a release and a completed click are different moments. Choose the event that matches the product behavior instead of attaching the same operation to every event. A repeated action, or a command sent too early, can be a design error even when the software is doing exactly what it was told.
This is a long-standing practical issue. A 2021 LVGL forum discussion asked when to save a changing slider value; later replies found that the expected event had never been registered. Make the event choice explicit and test it. Use the LVGL 9.0 event documentation for the API version you are on rather than copying an older forum example unchanged.
Keep the interface valid after it changes
The second test added a target button plus two controls: one resized and moved the target, the other deleted it. That matters for a product prototype because real interfaces change: settings panels close, controls become unavailable and screens are replaced.
The console logged the size change, and after the delete control it reported that the widget had been destroyed. That raises the obvious follow-up: what happens if someone presses the resize control again after the target is gone? The example needs to disable that control, or check the target's lifetime before reusing it. Our capture shows one deletion working, not that every later action is handled safely.
We also looked at the creation event. A callback attached after an object has already been constructed cannot catch that earlier event retroactively. If your code depends on setup work running at creation, initialize that state explicitly instead of expecting a late-registered callback to fire.
Let a group of controls share handling without losing identity
The final test put four child buttons inside a parent container. With event bubbling enabled, a child's event also reaches the parent. The parent can still identify the original button through the event target, while the current target identifies the object handling the event at that moment. In our run, the parent's handler correctly reported which of the four buttons had been clicked.
That lets a team build a grouped set of choices with one place responsible for handling them. The distinction between "where the action started" and "where it is being handled" keeps a shared callback from treating every button as the same command.
Turn the simulator into a useful handoff
For a review session, give each button a product-relevant label and write down the expected response next to it. Ask someone to navigate forward, back out, and repeat an action after the screen changes. Keep the console visible to the developer, and watch whether the user understands the on-screen feedback without it.
The simulator supports that interaction review, and it is fast to change. Touch accuracy, physical screen size, rendering speed and the response of connected hardware still have to be tested on the intended device. List those as the next integration work rather than waiting for final hardware before discussing the interface at all. A short table of screens, buttons, the event each one uses and the hardware action it should eventually trigger is a good handoff from the simulator stage to the board stage.