diff --git a/docs/images/release-1.0.0/firmware-tracker-card-native.png b/docs/images/release-1.0.0/firmware-tracker-card-native.png index 20e9b348..a2b47c32 100644 Binary files a/docs/images/release-1.0.0/firmware-tracker-card-native.png and b/docs/images/release-1.0.0/firmware-tracker-card-native.png differ diff --git a/modules/ui_shared/src/ui/components/bottom_bar.cpp b/modules/ui_shared/src/ui/components/bottom_bar.cpp index aa671e18..5402b057 100644 --- a/modules/ui_shared/src/ui/components/bottom_bar.cpp +++ b/modules/ui_shared/src/ui/components/bottom_bar.cpp @@ -42,6 +42,12 @@ lv_obj_t* create(lv_obj_t* parent) void style_item(lv_obj_t* item) { plain(item); + for (const auto state : {LV_STATE_FOCUSED, LV_STATE_CHECKED, LV_STATE_PRESSED}) + { + lv_obj_set_style_radius(item, 0, state); + lv_obj_set_style_border_width(item, 0, state); + lv_obj_set_style_shadow_width(item, 0, state); + } lv_obj_clear_flag(item, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_bg_opa(item, LV_OPA_TRANSP, LV_PART_MAIN); lv_obj_set_style_bg_color(item, lv_color_hex(kSelection), LV_STATE_FOCUSED); @@ -151,6 +157,12 @@ void Menu::present() lv_obj_set_style_pad_row(content_, 2, LV_PART_MAIN); lv_obj_set_style_margin_all(content_, 0, LV_PART_MAIN); lv_obj_set_height(content_, LV_SIZE_CONTENT); + // Font and padding must be final before measuring translated item labels. + for (uint32_t index = 0; index < lv_obj_get_child_count(content_); ++index) + { + auto* item = lv_obj_get_child(content_, index); + if (lv_obj_has_flag(item, LV_OBJ_FLAG_CLICKABLE)) style_item(item); + } lv_obj_update_layout(root_); fit_menu(content_, std::max(72, lv_obj_get_width(root_) - 8)); lv_obj_set_style_max_width(content_, lv_obj_get_width(root_) - 8, LV_PART_MAIN); @@ -168,11 +180,6 @@ void Menu::present() const auto x = valid(anchor_) ? anchor_area.x2 - root_area.x1 - width + 1 : lv_obj_get_width(root_) - width - 4; lv_obj_set_pos(content_, std::max(4, std::min(x, lv_obj_get_width(root_) - width - 4)), std::max(4, bottom - lv_obj_get_height(content_))); - for (uint32_t index = 0; index < lv_obj_get_child_count(content_); ++index) - { - auto* item = lv_obj_get_child(content_, index); - if (lv_obj_has_flag(item, LV_OBJ_FLAG_CLICKABLE)) style_item(item); - } prepare_keys(content_); } diff --git a/tests/site/tracker-page.test.mjs b/tests/site/tracker-page.test.mjs index 1f1d6a43..0a29f125 100644 --- a/tests/site/tracker-page.test.mjs +++ b/tests/site/tracker-page.test.mjs @@ -30,7 +30,11 @@ for(const device of ['pager','tdeck','wio-tracker-l2']) { assert.equal(stop.x,action.x,'Start and stop share a stable right-aligned position'); assert.ok(snapshot().some(item=>!item.button&&/^track-\d+$/.test(item.text)),`${device}: new recording has a readable title without the file extension`); if(device!=='pager')assert.ok(snapshot().some(item=>item.text==='GPX | Recording'),`${device}: recording card shows file type/status`); - else assert.ok(!snapshot().some(item=>item.text==='GPX | Recording'),'Short Pager recording card preserves room for location and actions by hiding the format row'); + else { + const format=snapshot().find(item=>item.text==='GPX | Recording'); + const counter=snapshot().find(item=>item.text==='1 / 1'); + if(format&&counter)assert.ok(format.y+format.h<=counter.y,'Pager format row leaves room for paging after recovering the full content width'); + } click('Routes'); assert.ok(!snapshot().some(item=>['Recording','Stop recording'].includes(item.text)),'Route mode has its own status and actions'); click('Tracks'); diff --git a/tests/site/two-pane-layout.test.mjs b/tests/site/two-pane-layout.test.mjs index 6bdcfd9c..13d4f26a 100644 --- a/tests/site/two-pane-layout.test.mjs +++ b/tests/site/two-pane-layout.test.mjs @@ -16,9 +16,11 @@ for(const [device,width,height] of [['pager',480,222],['tdeck',320,240],['wio-tr const tick=()=>{for(let i=0;i<5;i++)runtime._native_tick(50);}; const snapshot=()=>JSON.parse(runtime.UTF8ToString(runtime._native_snapshot())); const click=text=>{ - const button=snapshot().find(item=>item.button&&item.text===text); + const button=snapshot().findLast(item=>item.button&&item.text===text); assert.ok(button,`${device}: missing ${text}`); - runtime._native_activate(button.id);tick(); + const x=button.x+Math.floor(button.w/2),y=button.y+Math.floor(button.h/2); + runtime._native_pointer(x,y,1);runtime._native_tick(50); + runtime._native_pointer(x,y,0);tick(); }; for(const [page,filterText,contentText] of [[5,'Direct','Alex'],[12,'Contacts','No nodes'],[14,'Tracks','Not recording'],[11,'Profile'],[8,'Installed'],[9,'Fav']]) { runtime._native_open_page(page);tick(); diff --git a/tools/web_simulator/native_runtime.cpp b/tools/web_simulator/native_runtime.cpp index e5f6e47c..aa7f786d 100644 --- a/tools/web_simulator/native_runtime.cpp +++ b/tools/web_simulator/native_runtime.cpp @@ -18,6 +18,7 @@ lv_group_t* menu_g = nullptr; lv_obj_t* main_screen = nullptr; static lv_display_t* display; static lv_indev_t* pointer_device; +static lv_group_t* bound_input_group; static uint16_t pixels[480 * 240]; static int pointer_x, pointer_y; static bool pointer_down; @@ -38,16 +39,16 @@ EM_JS(void, request_menu, (), { void set_default_group(lv_group_t* group) { - if (pointer_device) lv_indev_set_group(pointer_device, group); + // Native keys/encoder are dispatched directly, independently of touch. + // A pointer indev cannot retain a keypad/encoder group in LVGL. + bound_input_group = group; lv_group_set_default(group); } static lv_group_t* input_group() { // Pages may clear LVGL's auto-add default after binding their input group. - // Read the bound device just as the firmware input drivers do. - auto* group = pointer_device ? lv_indev_get_group(pointer_device) : nullptr; - return group ? group : lv_group_get_default(); + return bound_input_group ? bound_input_group : lv_group_get_default(); } void ui_request_exit_to_menu() {