fix(ui): rebuild launcher focus atomically

This commit is contained in:
torlando-agent[bot]
2026-08-06 14:40:48 +00:00
parent eb0a3c0c9a
commit fa72e7e4c8
2 changed files with 56 additions and 3 deletions
+25 -3
View File
@@ -61,13 +61,35 @@ HomeScreen::HomeScreen() {
}
HomeScreen::~HomeScreen() { if (_screen) lv_obj_del(_screen); }
void HomeScreen::show() {
lv_obj_clear_flag(_screen, LV_OBJ_FLAG_HIDDEN); lv_obj_move_foreground(_screen);
lv_obj_clear_flag(_screen, LV_OBJ_FLAG_HIDDEN);
lv_obj_move_foreground(_screen);
auto* group = LVGL::LVGLInit::get_default_group();
if (group) { for (auto* button : _buttons) lv_group_add_obj(group, button); lv_group_focus_obj(_buttons[0]); }
if (group) {
// Rebuild the ordered launcher group without letting the first add emit
// an automatic focus event. Messages becomes the sole final owner.
lv_group_focus_freeze(group, true);
for (auto* button : _buttons) lv_group_add_obj(group, button);
lv_group_focus_freeze(group, false);
lv_group_focus_obj(_buttons[0]);
}
}
void HomeScreen::hide() {
auto* group = LVGL::LVGLInit::get_default_group();
if (group) for (auto* button : _buttons) lv_group_remove_obj(button);
if (group) {
// Removing the current owner makes LVGL synchronously focus another
// member. Detach every other launcher tile first and the owner last so
// no later tile becomes a transient focus owner during navigation.
lv_obj_t* focused = lv_group_get_focused(group);
bool focused_is_launcher = false;
for (auto* button : _buttons) {
if (button == focused) {
focused_is_launcher = true;
} else {
lv_group_remove_obj(button);
}
}
if (focused_is_launcher) lv_group_remove_obj(focused);
}
lv_obj_add_flag(_screen, LV_OBJ_FLAG_HIDDEN);
}
void HomeScreen::clicked(lv_event_t* event) {
@@ -154,6 +154,37 @@ def test_directory_rebuild_has_one_final_focus_owner():
assert rebuild.index("lv_group_focus_freeze(group,false);") < rebuild.rindex("lv_group_focus_obj")
def test_launcher_transition_has_one_final_focus_owner():
"""Leaving and restoring Home must not transiently focus later tiles."""
home = (INCLUDE / "HomeScreen.cpp").read_text()
show_start = home.index("void HomeScreen::show()")
hide_start = home.index("void HomeScreen::hide()", show_start)
show = home[show_start:hide_start]
hide_end = home.index("void HomeScreen::clicked", hide_start)
hide = home[hide_start:hide_end]
# Adding the first tile to an empty LVGL group auto-focuses it. Freeze the
# complete ordered rebuild and make Messages the sole explicit owner.
assert "lv_group_focus_freeze(group, true);" in show
assert "lv_group_focus_freeze(group, false);" in show
assert show.index("lv_group_focus_freeze(group, true);") < show.index("lv_group_add_obj")
assert show.index("lv_group_focus_freeze(group, false);") < show.index("lv_group_focus_obj(_buttons[0])")
# Hide must detach every non-owner before the current owner; removing an
# owner early makes LVGL focus each surviving launcher tile synchronously.
assert "lv_obj_t* focused = lv_group_get_focused(group);" in hide
non_owner_remove = re.search(
r"if \(button == focused\)\s*\{.*?\}\s*else\s*\{\s*lv_group_remove_obj\(button\);",
hide,
re.S,
)
assert non_owner_remove
assert "if (focused_is_launcher) lv_group_remove_obj(focused);" in hide
assert non_owner_remove.start() < hide.index(
"if (focused_is_launcher) lv_group_remove_obj(focused);"
)
def test_bounded_nomadnet_fonts_match_display_allowlist():
expected = set(range(0x20, 0x7F)) | set(range(0xA0, 0x180))
expected |= set(range(0x2190, 0x219A))