From e928ee4404a2f461c254060c72f941c5292eb406 Mon Sep 17 00:00:00 2001 From: Max Gramser Date: Fri, 18 Sep 2026 19:41:45 +0200 Subject: [PATCH] Take a slider let go within a centimetre of the glass edge as its end, because the panel loses a finger swiped off the screen before the slider's end (0.2.80) A fast swipe off the right side of a Guition ended at 93-98 %: the GT911 reports the finger for the last time 33-48 px inside the edge while a wide tile's slider ends 29 px from it, and LVGL keeps the last point it saw. cyd::edge_snap() takes a release inside the band (SLIDER_EDGE_SNAP_PX: Guition 67, CYD 56) as the end that lies in that band, for the tile sliders and the light card's sliders; the log names each snap. App 0.2.80, firmware 0.2.67. Co-Authored-By: Claude Fable 5.1 --- components/smart_display/cyd_ui.h | 13 +++++++++++++ components/smart_display/light_controls.h | 15 +++++++++++++++ components/smart_display/runtime_tiles.h | 9 +++++++++ guition-4848s040.yaml | 6 +++++- home-like-2432s028.yaml | 6 +++++- packages/cyd.yaml | 6 +++++- packages/guition.yaml | 6 +++++- screen_manager/CHANGELOG.md | 7 +++++++ screen_manager/app/core.py | 2 +- screen_manager/config.yaml | 2 +- tests/test_cyd_ui.cpp | 8 ++++++++ 11 files changed, 74 insertions(+), 6 deletions(-) diff --git a/components/smart_display/cyd_ui.h b/components/smart_display/cyd_ui.h index 19c20c4..1defbfe 100644 --- a/components/smart_display/cyd_ui.h +++ b/components/smart_display/cyd_ui.h @@ -219,4 +219,17 @@ inline bool release_jump(int held, int released, int minimum, int maximum) { const bool at_end = released <= minimum || released >= maximum; return at_end && std::abs(released - held) * 4 > maximum - minimum; } + +// A slider whose end lies within `band` pixels of the screen's edge cannot be dragged to that end: +// the panel loses a finger swiped off the glass before it gets there (a GT911 reports a fast swipe +// for the last time 30-50 px inside the edge), and LVGL keeps the last point it saw. A finger let go +// inside that band meant the end, as a pointer clamped to the bar does in Home Assistant's slider. +// `point`, `start` and `end` lie along the slider's axis; returns -1 for the start, 1 for the end. +inline int edge_snap(int point, int start, int end, int screen, int band) { + if (band <= 0 || screen <= 0) return 0; + if (end >= screen - band && point >= screen - band) return 1; + if (start < band && point < band) return -1; + return 0; +} +inline int edge_snap_band = 0; } // namespace cyd diff --git a/components/smart_display/light_controls.h b/components/smart_display/light_controls.h index 470469a..6164d7e 100644 --- a/components/smart_display/light_controls.h +++ b/components/smart_display/light_controls.h @@ -117,6 +117,21 @@ inline void event(lv_event_t *e) { if (code == LV_EVENT_PRESS_LOST) row.dirty = false; if (code == LV_EVENT_RELEASED && row.dirty) { row.dirty = false; + // A finger let go within the edge band of the glass meant the slider's end (cyd::edge_snap). + if (auto *indev = lv_indev_active()) { + lv_point_t p; + lv_indev_get_point(indev, &p); + lv_area_t a; + lv_obj_get_coords(row.slider, &a); + const int screen = lv_display_get_horizontal_resolution(lv_obj_get_display(row.slider)); + const int snap = cyd::edge_snap(p.x, a.x1, a.x2, screen, cyd::edge_snap_band); + if (snap) { + const int end = snap > 0 ? lv_slider_get_max_value(row.slider) : lv_slider_get_min_value(row.slider); + ESP_LOGI("slider", "Let go %d px from the %s edge: slider %d -> %d", snap > 0 ? screen - 1 - (int) p.x : (int) p.x, snap > 0 ? "right" : "left", lv_slider_get_value(row.slider), end); + lv_slider_set_value(row.slider, end, LV_ANIM_OFF); + preview(row); + } + } int value = lv_slider_get_value(row.slider); if (row.index == 0) active->hue = value; if (row.index == 1) { diff --git a/components/smart_display/runtime_tiles.h b/components/smart_display/runtime_tiles.h index edc13be..4c13bc0 100644 --- a/components/smart_display/runtime_tiles.h +++ b/components/smart_display/runtime_tiles.h @@ -895,6 +895,15 @@ inline void slider_event(lv_event_t *e){ // A slider among a card's own parts (the media tile's volume) belongs to the tile the slot shows now. for(auto &w:widgets)if(w.slider==slider || w.control_slider==slider || (w.extra && lv_obj_get_parent(slider)==w.extra)){index=w.index;break;} bool changed=slider_changed;captured_slider=nullptr;slider_changed=false; + // A finger let go within the edge band of the glass meant the slider's end (cyd::edge_snap). + if(auto *indev=lv_indev_active();indev && lv_obj_get_width(slider)>=lv_obj_get_height(slider)){ + lv_point_t p;lv_indev_get_point(indev,&p);lv_area_t a;lv_obj_get_coords(slider,&a); + int screen=lv_display_get_horizontal_resolution(lv_obj_get_display(slider)); + int snap=cyd::edge_snap(p.x,a.x1,a.x2,screen,cyd::edge_snap_band); + if(snap){int end=snap>0?(int)lv_slider_get_max_value(slider):std::max(0,(int)lv_slider_get_min_value(slider)); + ESP_LOGI("slider","Let go %d px from the %s edge: slider %d -> %d",snap>0?screen-1-(int)p.x:(int)p.x,snap>0?"right":"left",lv_slider_get_value(slider),end); + lv_slider_set_value(slider,end,LV_ANIM_OFF);changed=true;} + } if(changed && cyd::touch_guard.accept_slider(esphome::millis(),200+index))commit_slider(index,lv_slider_get_value(slider)); } } diff --git a/guition-4848s040.yaml b/guition-4848s040.yaml index 3942dab..986507c 100644 --- a/guition-4848s040.yaml +++ b/guition-4848s040.yaml @@ -2,7 +2,7 @@ # Source profile for packages/guition.yaml (tools/generate_packages.py); see docs/GUITION.md. # A screen gets its tiles from the ESP Screen Manager add-on while it runs. The CYD is its own profile. substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.66" + SCREEN_FIRMWARE_VERSION: "0.2.67" # 0: no drift limit. A press that drifts is still a tap as long as the finger lets go on the tile # (runtime_tiles::event checks where it let go: LVGL keeps the press on the tile and would click it # anywhere), and a quick flick is LVGL's gesture, never a tap (the home_page gesture handler in on_boot). @@ -14,6 +14,9 @@ substitutions: # this much travel inward (about 6 mm); a swipe from the middle of the screen does nothing. EDGE_SWIPE_BAND_PX: "32" EDGE_SWIPE_TRAVEL_PX: "40" + # A slider let go this close to the left or right edge of the glass (about 1 cm) meant its end when + # that end lies in the band: the panel loses a finger swiped off the glass 30-50 px before the edge. + SLIDER_EDGE_SNAP_PX: "67" DASHBOARD_INBOX_INTERNAL: "false" DEVICE_NAME: "guition-new" DEVICE_FRIENDLY_NAME: "My Guition" @@ -127,6 +130,7 @@ esphome: - lambda: |- cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS}); cyd::edge_swipe.configure(${DISPLAY_W}, ${DISPLAY_H}, ${EDGE_SWIPE_BAND_PX}, ${EDGE_SWIPE_TRAVEL_PX}); + cyd::edge_snap_band = ${SLIDER_EDGE_SNAP_PX}; // The page and the tile container take no presses: the theme's pressed style would // otherwise repaint nearly the whole screen twice per touch in the empty edge band, // starving the touch polling for the whole length of a quick swipe. diff --git a/home-like-2432s028.yaml b/home-like-2432s028.yaml index ceac54a..2df31de 100644 --- a/home-like-2432s028.yaml +++ b/home-like-2432s028.yaml @@ -17,11 +17,14 @@ ############################################################ substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.66" + SCREEN_FIRMWARE_VERSION: "0.2.67" # A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe. TOUCH_MOVE_LIMIT_PX: "56" # Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not. TOUCH_MIN_PRESS_MS: "60" + # A slider let go this close to the left or right edge of the glass (about 1 cm) meant its end when + # that end lies in the band: a panel loses a finger swiped off the glass before the edge. + SLIDER_EDGE_SNAP_PX: "56" DASHBOARD_INBOX_INTERNAL: "false" DEVICE_NAME: "cyd-new" DEVICE_FRIENDLY_NAME: "My CYD" @@ -247,6 +250,7 @@ esphome: // Per-panel affine correction; shared defaults are neutral. id(ts_touch).set_raw_correction(${TOUCH_AFFINE_XX}f, ${TOUCH_AFFINE_XY}f, ${TOUCH_AFFINE_XC}f, ${TOUCH_AFFINE_YX}f, ${TOUCH_AFFINE_YY}f, ${TOUCH_AFFINE_YC}f); cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS}); + cyd::edge_snap_band = ${SLIDER_EDGE_SNAP_PX}; id(last_touch_ms) = millis(); id(last_use_ms) = millis(); id(display_dimmed) = false; diff --git a/packages/cyd.yaml b/packages/cyd.yaml index 2361d23..b2e7598 100644 --- a/packages/cyd.yaml +++ b/packages/cyd.yaml @@ -18,11 +18,14 @@ ############################################################ substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.66" + SCREEN_FIRMWARE_VERSION: "0.2.67" # A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe. TOUCH_MOVE_LIMIT_PX: "56" # Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not. TOUCH_MIN_PRESS_MS: "60" + # A slider let go this close to the left or right edge of the glass (about 1 cm) meant its end when + # that end lies in the band: a panel loses a finger swiped off the glass before the edge. + SLIDER_EDGE_SNAP_PX: "56" DASHBOARD_INBOX_INTERNAL: "false" DEVICE_NAME: "cyd-new" DEVICE_FRIENDLY_NAME: "My CYD" @@ -251,6 +254,7 @@ esphome: // Per-panel affine correction; shared defaults are neutral. id(ts_touch).set_raw_correction(${TOUCH_AFFINE_XX}f, ${TOUCH_AFFINE_XY}f, ${TOUCH_AFFINE_XC}f, ${TOUCH_AFFINE_YX}f, ${TOUCH_AFFINE_YY}f, ${TOUCH_AFFINE_YC}f); cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS}); + cyd::edge_snap_band = ${SLIDER_EDGE_SNAP_PX}; id(last_touch_ms) = millis(); id(last_use_ms) = millis(); id(display_dimmed) = false; diff --git a/packages/guition.yaml b/packages/guition.yaml index 1358444..d55957b 100644 --- a/packages/guition.yaml +++ b/packages/guition.yaml @@ -3,7 +3,7 @@ # Source profile for packages/guition.yaml (tools/generate_packages.py); see docs/GUITION.md. # A screen gets its tiles from the ESP Screen Manager add-on while it runs. The CYD is its own profile. substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.66" + SCREEN_FIRMWARE_VERSION: "0.2.67" # 0: no drift limit. A press that drifts is still a tap as long as the finger lets go on the tile # (runtime_tiles::event checks where it let go: LVGL keeps the press on the tile and would click it # anywhere), and a quick flick is LVGL's gesture, never a tap (the home_page gesture handler in on_boot). @@ -15,6 +15,9 @@ substitutions: # this much travel inward (about 6 mm); a swipe from the middle of the screen does nothing. EDGE_SWIPE_BAND_PX: "32" EDGE_SWIPE_TRAVEL_PX: "40" + # A slider let go this close to the left or right edge of the glass (about 1 cm) meant its end when + # that end lies in the band: the panel loses a finger swiped off the glass 30-50 px before the edge. + SLIDER_EDGE_SNAP_PX: "67" DASHBOARD_INBOX_INTERNAL: "false" DEVICE_NAME: "guition-new" DEVICE_FRIENDLY_NAME: "My Guition" @@ -131,6 +134,7 @@ esphome: - lambda: |- cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS}); cyd::edge_swipe.configure(${DISPLAY_W}, ${DISPLAY_H}, ${EDGE_SWIPE_BAND_PX}, ${EDGE_SWIPE_TRAVEL_PX}); + cyd::edge_snap_band = ${SLIDER_EDGE_SNAP_PX}; // The page and the tile container take no presses: the theme's pressed style would // otherwise repaint nearly the whole screen twice per touch in the empty edge band, // starving the touch polling for the whole length of a quick swipe. diff --git a/screen_manager/CHANGELOG.md b/screen_manager/CHANGELOG.md index 784550c..2d5550e 100644 --- a/screen_manager/CHANGELOG.md +++ b/screen_manager/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.2.80 (firmware 0.2.67) + +A slider swiped off the edge of the glass goes all the way. + +- **Swiping a slider off the screen sets it to the end.** A fast swipe to the right that leaves the glass ended at 93 to 98 % instead of 100 %: a capacitive panel loses a fast finger 30 to 50 px before the edge, and a wide tile's slider ends only 29 px from it, so the screen never saw the finger past the end (measured on a Guition: the last touch landed 33 to 48 px inside the edge, seven times out of seven). A slider whose end lies within about a centimetre of the left or right edge now takes a finger let go in that band as that end: off the right is 100 %, off the left is the minimum (1 % for a light, as in Home Assistant, where a pointer past the bar's end counts as its end too). Letting go anywhere else keeps the exact value. Tile sliders and the sliders on the light card do this; `SLIDER_EDGE_SNAP_PX` in the board profile sets the band (Guition 67 px, CYD 56 px, 0 turns it off). The log says when it happened (`Let go 39 px from the right edge: slider 973 -> 1000`). +- Needs firmware 0.2.67: press **Update** on the screen. Includes everything from 0.2.79. CYD firmware: 1,652,784 bytes, 90.1 % of the update slot. + ## 0.2.79 (firmware 0.2.66) A blind's bar reads the same way on the tile and on its card. diff --git a/screen_manager/app/core.py b/screen_manager/app/core.py index d1d6c17..342cadb 100644 --- a/screen_manager/app/core.py +++ b/screen_manager/app/core.py @@ -37,7 +37,7 @@ WEEKDAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] REPO = 'https://github.com/MaxGramser/homeassistant_espscreen' REFS = {'cyd': 'main', 'guition': 'main'} # Firmware shipped with this app release; screens below it get an update offer. -FIRMWARE_VERSION = '0.2.66' +FIRMWARE_VERSION = '0.2.67' # The Auto standby switch a screen offers Home Assistant automations. AUTO_STANDBY_MIN_FIRMWARE = '0.2.41' # The settings page the screen opens itself, and the screen.settings tile that opens it. diff --git a/screen_manager/config.yaml b/screen_manager/config.yaml index a620ac2..582ed68 100644 --- a/screen_manager/config.yaml +++ b/screen_manager/config.yaml @@ -1,5 +1,5 @@ name: ESP Screen Manager -version: "0.2.79" +version: "0.2.80" slug: esp_screen_manager description: Choose tiles for your CYD and Guition touchscreen, without reflashing. url: https://github.com/MaxGramser/homeassistant_espscreen diff --git a/tests/test_cyd_ui.cpp b/tests/test_cyd_ui.cpp index da41f1b..ec4a453 100644 --- a/tests/test_cyd_ui.cpp +++ b/tests/test_cyd_ui.cpp @@ -168,4 +168,12 @@ int main() { assert(!cyd::release_jump(20, 0, 0, 360)); // dragged to the start and let go assert(!cyd::release_jump(240, 238, 0, 360)); // a few pixels on lift-off assert(cyd::release_jump(80, -6, -6, 100)); // brightness: 80 % to the stub below 1 % + // A slider let go within the edge band of the glass meant the end that lies in that band. + assert(cyd::edge_snap(437, 29, 450, 480, 67) == 1); // wide tile, a fast swipe the panel lost at 437 + assert(cyd::edge_snap(431, 29, 450, 480, 67) == 1); + assert(cyd::edge_snap(412, 29, 450, 480, 67) == 0); // let go just before the band + assert(cyd::edge_snap(437, 29, 222, 480, 67) == 0); // a narrow tile's end is far from the edge + assert(cyd::edge_snap(20, 29, 450, 480, 67) == -1); // off the left: the start + assert(cyd::edge_snap(20, 258, 452, 480, 67) == 0); // the right column's start is not in the band + assert(cyd::edge_snap(437, 29, 450, 480, 0) == 0); // band off }