Keep a slider's value when you let go, and stop needless full resends (0.2.55)

A light's colour slider on Studio 1 sometimes sent red on release. Measured
live: an automation with a state trigger on a six-lamp group (no `to:`) set
"Standby after" to its current value after every colour change. The screen
reported the setting, ESP Screen Manager saved the layout and resent layout,
header and every tile state, and the screen drew its whole page again,
reading no touch input meanwhile. The GT911's stray (0, 0) contact then
became the finger's last position, and LVGL 9.5 set the slider from it on
release. Both red commits left 75-85 ms after such a resync.

The app no longer saves or resends a setting event whose value the layout
already has. The four setting numbers return before the preference write
and the event when nothing changed. A layout message that changes nothing
on the screen no longer runs layout_changed() and refresh_all(); the tile
states after it redraw their own tiles. The Guition wraps the LVGL touch
read callback with cyd::GhostTouch, which repeats the previous read for a
pressed (0, 0), and the edge swipe skips that sample too. Both slider
handlers log a jump to an end on release.

Checked with 214 Python and 14 C++ tests, generate_packages --check, and
ESP32 builds of both Easy profiles. Firmware 0.2.47.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-16 11:15:40 +02:00
parent f3af5c2125
commit 6df967bfef
15 changed files with 393 additions and 12 deletions
+19
View File
@@ -136,4 +136,23 @@ int main() {
assert(rollover.accept(50, 1)); // millis wraps after 49 days
rollover.begin(60);
assert(!rollover.accept(130, 1));
// GT911 stray (0, 0): measured on Studio 1 as the last sample of a drag before lift-off.
cyd::GhostTouch ghost;
auto same = [](cyd::PointerRead a, bool pressed, int x, int y) { return a.pressed == pressed && a.x == x && a.y == y; };
assert(same(ghost.filter({false, 0, 0}, 0, 0), false, 0, 0)); // released: nothing to hide
assert(same(ghost.filter({true, 0, 0}, 0, 0), false, 0, 0)); // a touch cannot start in the corner
assert(same(ghost.filter({true, 302, 250}, 0, 0), true, 302, 250)); // the finger on blue
assert(same(ghost.filter({true, 0, 0}, 0, 0), true, 302, 250)); // stray sample: still on blue
assert(same(ghost.filter({true, 0, 0}, 0, 0), true, 302, 250)); // and again
assert(same(ghost.filter({false, 302, 250}, 0, 0), false, 302, 250)); // released where the finger was
assert(ghost.dropped() == 3);
assert(same(ghost.filter({true, 0, 240}, 0, 0), true, 0, 240)); // the left edge itself is a real touch
assert(same(ghost.filter({true, 479, 479}, 479, 479), true, 0, 240)); // rotated 180: (0, 0) is that corner
assert(same(ghost.filter({true, 0, 0}, 479, 479), true, 0, 0)); // and the top-left is a real pixel there
// A dragged slider sent to its end on release is reported; a drag that ends there is not a jump.
assert(cyd::release_jump(240, 0, 0, 360)); // hue: blue to red at the start
assert(cyd::release_jump(120, 360, 0, 360)); // green to red at the end
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 %
}