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 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-18 19:41:45 +02:00
parent 788f42da65
commit e928ee4404
11 changed files with 74 additions and 6 deletions
+13
View File
@@ -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
+15
View File
@@ -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) {
+9
View File
@@ -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));
}
}
+5 -1
View File
@@ -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.
+5 -1
View File
@@ -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;
+5 -1
View File
@@ -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;
+5 -1
View File
@@ -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.
+7
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+8
View File
@@ -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
}