Feed the edge swipe from the touchscreen triggers again (0.2.34)

The 0.2.32 edge swipe listened on LVGL's input device, but the LVGL 9.5.0
bundled by ESPHome sends the indev PRESSED and RELEASED without PRESSING,
so the swipe never received a position update: on Studio 1 the log showed
the GT911 press and then nothing. The Guition follows the swipe through
on_touch, on_update and on_release again, the route that worked live in
0.2.24 once the card-close fix was in, with ESPHome's own rotation mapping,
the 45-degree rule, end() on release so a stale armed state cannot fire on
the next touch, and the "genegeerd"/"niet gevuurd" log lines. Firmware
0.2.29 for both boards; the app only moves its target version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-14 13:40:36 +02:00
parent e3efbbaa06
commit 7564e612bb
11 changed files with 139 additions and 92 deletions
+26 -10
View File
@@ -130,18 +130,25 @@ class TouchGuard {
inline TouchGuard touch_guard;
// Page navigation by swiping in from a side edge (Guition, firmware 0.2.24+). Fed from
// LVGL's own pointer events (PRESSED/PRESSING), so the coordinates are already rotated the
// way the user sees the screen. A touch that starts within `band` pixels of the left or
// right edge and travels `travel` pixels inward, more sideways than up or down, flips one
// page: from the right edge leftwards is "next", from the left edge rightwards "previous".
// No speed requirement; a touch that starts in the middle never counts, so tapping and
// dragging on tiles cannot change the page by accident.
// ESPHome's touchscreen triggers: on_touch begins, on_update updates, on_release ends.
// LVGL 9.5 does not send PRESSING to the input device, so the native touch points are
// used and rotated here the way ESPHome's LVGL component rotates its pointer. A touch that
// starts within `band` pixels of the left or right edge of the screen as the user sees it
// and travels `travel` pixels inward, more sideways than up or down, flips one page: from
// the right edge leftwards is "next", from the left edge rightwards "previous". No speed
// requirement; a touch that starts in the middle never counts, so tapping and dragging on
// tiles cannot change the page by accident.
class EdgeSwipe {
public:
void configure(int band, int travel) { band_ = band; travel_ = travel; }
// `width` is the screen width as the user sees it (LVGL's horizontal resolution).
void begin(int x, int y, int width) {
void configure(int width, int height, int band, int travel) {
width_ = width; height_ = height; band_ = band; travel_ = travel;
}
// Native touch coordinates plus the LVGL rotation in degrees (0, 90, 180, 270).
void begin(int x, int y, int rotation = 0) {
rotation_ = rotation;
rotate(x, y);
start_x_ = x; start_y_ = y;
const int width = (rotation == 90 || rotation == 270) ? height_ : width_;
from_ = x < band_ ? 1 : x >= width - band_ ? -1 : 0; // 1: left edge, -1: right edge
done_ = false;
inward_ = sideways_ = 0;
@@ -150,6 +157,7 @@ class EdgeSwipe {
// +1 next page, -1 previous page, 0 nothing; fires at most once per touch.
int update(int x, int y) {
if (!armed()) return 0;
rotate(x, y);
const int dx = x - start_x_, dy = y - start_y_;
const int inward = from_ == 1 ? dx : -dx;
inward_ = std::max(inward_, inward);
@@ -158,11 +166,19 @@ class EdgeSwipe {
done_ = true;
return from_ == 1 ? -1 : 1;
}
// The finger left the glass: a stale armed state must never fire on the next touch
// (ESPHome runs on_update before on_touch in the first cycle of a new touch).
void end() { done_ = true; }
// How far the finger got, for the log when an edge touch ends without a page flip.
int inward() const { return inward_; }
int sideways() const { return sideways_; }
private:
int band_{32}, travel_{40};
void rotate(int &x, int &y) const {
if (rotation_ == 90) { const int tmp = y; y = width_ - x - 1; x = tmp; }
else if (rotation_ == 180) { x = width_ - x - 1; y = height_ - y - 1; }
else if (rotation_ == 270) { const int tmp = x; x = height_ - y - 1; y = tmp; }
}
int width_{480}, height_{480}, band_{32}, travel_{40}, rotation_{0};
int start_x_{0}, start_y_{0}, from_{0}, inward_{0}, sideways_{0};
bool done_{true};
};
+9
View File
@@ -177,6 +177,15 @@ staan iconen in de browser uit het midden); draai daarna `generate_packages.py`.
De substituties `MDI_GLYPH_*` zijn vervallen: `TILEn_ICON` in handmatige
profielen moet uit de set komen. Geen gewijzigde preferences of sleutels.
### Compatibiliteit 0.2.34 / firmware 0.2.29
Alleen firmware. LVGL 9.5.0's `send_event()` in `lv_indev.c` stuurt het invoerapparaat
PRESSED, RELEASED, CLICKED, LONG_PRESSED en KEY, maar geen PRESSING; een
`lv_indev_add_event_cb` krijgt dus geen positie-updates tijdens een druk. `cyd::EdgeSwipe`
werkt daarom weer op de native touchpunten uit `on_touch`/`on_update`/`on_release`
(`configure(width, height, band, travel)`, `begin(x, y, rotation)` met ESPHome's
rotatiemapping, `update()`, `end()`), de indev-callback is weg. Verder ongewijzigd.
### Compatibiliteit 0.2.33 / firmware 0.2.28
Alleen firmware. `TouchGuard::configure(0, ...)` schakelt de verplaatsingsgrens uit
+34
View File
@@ -0,0 +1,34 @@
# Testresultaten app 0.2.34 / firmware 0.2.29 (2026-09-14)
Hotfix: de randveeg uit 0.2.32/0.2.33 vuurde nooit. Live op Studio 1 (firmware 0.2.28):
`GT911 press x=5` en `x=477`, daarna niets, ook geen `randveeg genegeerd` of
`randveeg niet gevuurd`. Oorzaak in de meegeleverde LVGL 9.5.0 (`lv_indev.c`,
`send_event()`): het invoerapparaat krijgt PRESSED, RELEASED, CLICKED, LONG_PRESSED en KEY,
maar geen PRESSING, dus de `lv_indev_add_event_cb`-veeg kreeg nooit positie-updates.
## Wat veranderde
- `cyd::EdgeSwipe` werkt weer op de native touchpunten van ESPHome's `on_touch`
(`begin` met de LVGL-rotatie), `on_update` (`update`, paginawissel met
`touch_guard.consume()` en `lv_indev_wait_release` op elk invoerapparaat) en
`on_release` (`end()`, plus de logregel `randveeg niet gevuurd`). Dezelfde route als
0.2.24, die live werkte zodra de kaartfix (`close_cards`, 0.2.32) erbij zat.
- `end()` bij loslaten voorkomt dat een niet-afgemaakte veeg bij de eerste `on_update` van
de volgende aanraking (ESPHome vuurt die vóór `on_touch`) alsnog afgaat.
- De indev-callback is verwijderd; 45°-regel en `randveeg genegeerd: <reden>` blijven.
## Geautomatiseerd
- C++ (clang++ -std=c++17 -Wall -Wextra -Werror -I.): 11/11 PASS; `test_cyd_ui` dekt de
rotaties, `end()` en de logwaarden.
- Python: 91 tests OK (`FIRMWARE_VERSION` 0.2.29 in app, profielen en pakketten);
`generate_packages.py --check` groen; bench-kopieën opnieuw gegenereerd.
- ESPHome 2026.6.2 compile: `easy-guition-device.yaml` SUCCESS in 39 s (RAM 32,0%, flash
18,3%), `easy-cyd-device.yaml` SUCCESS in 36 s (RAM 31,0%, flash 76,1%); geen nieuwe
waarschuwingen.
## Te controleren op Studio 1 na de update
- Vegen vanaf beide randen zonder eerst een kaart te sluiten; na een lichtkaart openen en
sluiten; en de eerste veeg direct na een paginawissel. Het log meldt per veeg
`randveeg: pagina …`, `randveeg genegeerd: …` of `randveeg niet gevuurd: …`.
+21 -33
View File
@@ -1,7 +1,7 @@
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.28"
SCREEN_FIRMWARE_VERSION: "0.2.29"
# 0: no drift limit; LVGL decides, releasing inside the tile is a tap, leaving it is not
# (like iOS). The CYD keeps a limit because its resistive panel jumps.
TOUCH_MOVE_LIMIT_PX: "0"
@@ -597,38 +597,7 @@ esphome:
then:
- lambda: |-
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
cyd::edge_swipe.configure(${EDGE_SWIPE_BAND_PX}, ${EDGE_SWIPE_TRAVEL_PX});
// Edge swipe on LVGL's own pointer events: the point is already rotated, the callback
// runs inside LVGL, and lv_indev_wait_release keeps the tile under the finger quiet.
if (auto *pointer = lv_indev_get_next(nullptr)) {
lv_indev_add_event_cb(pointer, [](lv_event_t *e) {
auto *indev = static_cast<lv_indev_t *>(lv_event_get_current_target(e));
const auto code = lv_event_get_code(e);
lv_point_t p;
lv_indev_get_point(indev, &p);
if (code == LV_EVENT_PRESSED) {
cyd::edge_swipe.begin(p.x, p.y, lv_display_get_horizontal_resolution(lv_display_get_default()));
return;
}
if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
if (cyd::edge_swipe.armed() && cyd::edge_swipe.inward() > 0)
ESP_LOGI("touch", "randveeg niet gevuurd: %d px naar binnen, %d px verticaal", cyd::edge_swipe.inward(), cyd::edge_swipe.sideways());
return;
}
if (code != LV_EVENT_PRESSING) return;
const int step = cyd::edge_swipe.update(p.x, p.y);
if (!step) return;
const char *blocked = !runtime_tiles::enabled ? "geen runtime-tegels" : !runtime_tiles::swipe_pages ? "instelling uit"
: id(display_dimmed) ? "scherm gedimd" : id(calibration_active) ? "kalibratie" : !id(active_entity).empty() ? "kaart open"
: (runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN)) ? "detailkaart open" : nullptr;
if (blocked) { ESP_LOGI("touch", "randveeg genegeerd: %s", blocked); return; }
cyd::touch_guard.consume();
lv_indev_wait_release(indev);
ESP_LOGI("touch", "randveeg: pagina %d -> %d", id(tile_page), id(tile_page) + step);
id(tile_page) += step;
id(show_tile_page).execute();
}, LV_EVENT_ALL, nullptr);
}
cyd::edge_swipe.configure(${DISPLAY_W}, ${DISPLAY_H}, ${EDGE_SWIPE_BAND_PX}, ${EDGE_SWIPE_TRAVEL_PX});
id(last_touch_ms) = millis();
id(display_dimmed) = false;
lv_obj_set_style_pad_row(id(vacuum_actions), 4, 0);
@@ -1902,6 +1871,7 @@ touchscreen:
id(last_touch_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
cyd::edge_swipe.begin(touch.x, touch.y, static_cast<int>(id(screen_lvgl).get_rotation()));
ESP_LOGI("touch", "GT911 press x=%u y=%u test=%d", touch.x, touch.y, id(calibration_active));
on_update:
- lambda: |-
@@ -1909,11 +1879,29 @@ touchscreen:
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y, point.id);
if (point.id != cyd::touch_guard.contact()) continue;
// Swiping in from a side edge flips the page ("Vegen tussen pagina's"); the tap
// under the finger is consumed and LVGL waits for the release. LVGL 9.5 sends no
// PRESSING to the input device, hence the touchscreen trigger.
const int step = cyd::edge_swipe.update(point.x, point.y);
if (!step) continue;
const char *blocked = !runtime_tiles::enabled ? "geen runtime-tegels" : !runtime_tiles::swipe_pages ? "instelling uit"
: id(display_dimmed) ? "scherm gedimd" : id(calibration_active) ? "kalibratie" : !id(active_entity).empty() ? "kaart open"
: (runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN)) ? "detailkaart open" : nullptr;
if (blocked) { ESP_LOGI("touch", "randveeg genegeerd: %s", blocked); continue; }
cyd::touch_guard.consume();
for (auto *indev = lv_indev_get_next(nullptr); indev; indev = lv_indev_get_next(indev)) lv_indev_wait_release(indev);
ESP_LOGI("touch", "randveeg: pagina %d -> %d", id(tile_page), id(tile_page) + step);
id(tile_page) += step;
id(show_tile_page).execute();
}
on_release:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = false;
if (cyd::edge_swipe.armed() && cyd::edge_swipe.inward() > 0)
ESP_LOGI("touch", "randveeg niet gevuurd: %d px naar binnen, %d px verticaal", cyd::edge_swipe.inward(), cyd::edge_swipe.sideways());
cyd::edge_swipe.end();
# Native ESPHome driver with the working manufacturer's LVGL demo timings.
+1 -1
View File
@@ -58,7 +58,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.28"
SCREEN_FIRMWARE_VERSION: "0.2.29"
# 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.
+1 -1
View File
@@ -59,7 +59,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.28"
SCREEN_FIRMWARE_VERSION: "0.2.29"
# 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.
+21 -33
View File
@@ -2,7 +2,7 @@
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.28"
SCREEN_FIRMWARE_VERSION: "0.2.29"
# 0: no drift limit; LVGL decides, releasing inside the tile is a tap, leaving it is not
# (like iOS). The CYD keeps a limit because its resistive panel jumps.
TOUCH_MOVE_LIMIT_PX: "0"
@@ -601,38 +601,7 @@ esphome:
then:
- lambda: |-
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
cyd::edge_swipe.configure(${EDGE_SWIPE_BAND_PX}, ${EDGE_SWIPE_TRAVEL_PX});
// Edge swipe on LVGL's own pointer events: the point is already rotated, the callback
// runs inside LVGL, and lv_indev_wait_release keeps the tile under the finger quiet.
if (auto *pointer = lv_indev_get_next(nullptr)) {
lv_indev_add_event_cb(pointer, [](lv_event_t *e) {
auto *indev = static_cast<lv_indev_t *>(lv_event_get_current_target(e));
const auto code = lv_event_get_code(e);
lv_point_t p;
lv_indev_get_point(indev, &p);
if (code == LV_EVENT_PRESSED) {
cyd::edge_swipe.begin(p.x, p.y, lv_display_get_horizontal_resolution(lv_display_get_default()));
return;
}
if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
if (cyd::edge_swipe.armed() && cyd::edge_swipe.inward() > 0)
ESP_LOGI("touch", "randveeg niet gevuurd: %d px naar binnen, %d px verticaal", cyd::edge_swipe.inward(), cyd::edge_swipe.sideways());
return;
}
if (code != LV_EVENT_PRESSING) return;
const int step = cyd::edge_swipe.update(p.x, p.y);
if (!step) return;
const char *blocked = !runtime_tiles::enabled ? "geen runtime-tegels" : !runtime_tiles::swipe_pages ? "instelling uit"
: id(display_dimmed) ? "scherm gedimd" : id(calibration_active) ? "kalibratie" : !id(active_entity).empty() ? "kaart open"
: (runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN)) ? "detailkaart open" : nullptr;
if (blocked) { ESP_LOGI("touch", "randveeg genegeerd: %s", blocked); return; }
cyd::touch_guard.consume();
lv_indev_wait_release(indev);
ESP_LOGI("touch", "randveeg: pagina %d -> %d", id(tile_page), id(tile_page) + step);
id(tile_page) += step;
id(show_tile_page).execute();
}, LV_EVENT_ALL, nullptr);
}
cyd::edge_swipe.configure(${DISPLAY_W}, ${DISPLAY_H}, ${EDGE_SWIPE_BAND_PX}, ${EDGE_SWIPE_TRAVEL_PX});
id(last_touch_ms) = millis();
id(display_dimmed) = false;
lv_obj_set_style_pad_row(id(vacuum_actions), 4, 0);
@@ -1833,6 +1802,7 @@ touchscreen:
id(last_touch_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
cyd::edge_swipe.begin(touch.x, touch.y, static_cast<int>(id(screen_lvgl).get_rotation()));
ESP_LOGI("touch", "GT911 press x=%u y=%u test=%d", touch.x, touch.y, id(calibration_active));
on_update:
- lambda: |-
@@ -1840,11 +1810,29 @@ touchscreen:
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y, point.id);
if (point.id != cyd::touch_guard.contact()) continue;
// Swiping in from a side edge flips the page ("Vegen tussen pagina's"); the tap
// under the finger is consumed and LVGL waits for the release. LVGL 9.5 sends no
// PRESSING to the input device, hence the touchscreen trigger.
const int step = cyd::edge_swipe.update(point.x, point.y);
if (!step) continue;
const char *blocked = !runtime_tiles::enabled ? "geen runtime-tegels" : !runtime_tiles::swipe_pages ? "instelling uit"
: id(display_dimmed) ? "scherm gedimd" : id(calibration_active) ? "kalibratie" : !id(active_entity).empty() ? "kaart open"
: (runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN)) ? "detailkaart open" : nullptr;
if (blocked) { ESP_LOGI("touch", "randveeg genegeerd: %s", blocked); continue; }
cyd::touch_guard.consume();
for (auto *indev = lv_indev_get_next(nullptr); indev; indev = lv_indev_get_next(indev)) lv_indev_wait_release(indev);
ESP_LOGI("touch", "randveeg: pagina %d -> %d", id(tile_page), id(tile_page) + step);
id(tile_page) += step;
id(show_tile_page).execute();
}
on_release:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = false;
if (cyd::edge_swipe.armed() && cyd::edge_swipe.inward() > 0)
ESP_LOGI("touch", "randveeg niet gevuurd: %d px naar binnen, %d px verticaal", cyd::edge_swipe.inward(), cyd::edge_swipe.sideways());
cyd::edge_swipe.end();
# Native ESPHome driver with the working manufacturer's LVGL demo timings.
+5
View File
@@ -1,3 +1,8 @@
## 0.2.34 (firmware 0.2.29)
- **Randveeg werkte in 0.2.32 en 0.2.33 helemaal niet, hersteld.** De veeg was daar op de pointer-events van LVGL gezet, maar de LVGL 9.5.0 die ESPHome meelevert stuurt het invoerapparaat wel PRESSED en RELEASED maar geen PRESSING, dus de veeg kreeg nooit positie-updates (live gezien op Studio 1: wel `GT911 press`, verder niets). De Guition volgt de veeg weer via ESPHome's touchscreen-triggers, zoals in 0.2.24 (dat werkte live zodra de kaartfix uit 0.2.32 erbij zat), met de eigen rotatiemapping gelijk aan die van ESPHome's LVGL-component, de 45°-regel, `end()` bij loslaten (een niet-afgemaakte veeg kan daardoor niet meer bij de volgende aanraking afgaan) en de logregels `randveeg genegeerd: …` en `randveeg niet gevuurd: …`.
- Firmware 0.2.29 voor beide borden; de app biedt de update aan. Alleen de doelversie verandert in de app.
## 0.2.33 (firmware 0.2.28)
- **Guition: loslaten binnen de tegel telt.** De verplaatsingsgrens voor een tik staat op de Guition uit (`TOUCH_MOVE_LIMIT_PX: "0"`); LVGL beslist, zoals iOS: laat je los binnen de tegel waarop je begon, dan is het een tik, verlaat je vinger de tegel, dan niet. Een stevige druk die een centimeter verschoof (73 px op Studio 1) werd anders nog geweigerd. De randveeg neemt zijn eigen tik weg en sliders vangen hun eigen sleep, dus de grens had daar geen taak meer. De CYD houdt zijn 56 px, omdat het resistieve paneel kan springen.
+1 -1
View File
@@ -16,7 +16,7 @@ WEEKDAYS = ['ma', 'di', 'wo', 'do', 'vr', 'za', 'zo']
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.28'
FIRMWARE_VERSION = '0.2.29'
ATTRS = frozenset('brightness percentage current_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hvac_action hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement battery_level fan_speed volume_level is_volume_muted media_title options min max step temperature_unit supported_features device_class next_rising next_setting finishes_at duration remaining humidity wind_speed wind_speed_unit apparent_temperature fan_modes swing_modes fan_mode swing_mode'.split())
# Attributes whose boolean value the screen needs; every other bool stays behind.
BOOL_ATTRS = frozenset(['is_volume_muted'])
+1 -1
View File
@@ -1,5 +1,5 @@
name: ESP Screen Manager
version: "0.2.33"
version: "0.2.34"
slug: esp_screen_manager
description: Kies tegels voor je CYD en Guition touchscreen, zonder opnieuw flashen.
url: https://github.com/MaxGramser/homeassistant_espscreen
+19 -12
View File
@@ -98,32 +98,39 @@ int main() {
resistive.update(50, 180); // 70 px past the settled point: a swipe
assert(!resistive.accept(1100, 1));
assert(!resistive.accept_repeat(1100, 1));
// Edge swipe (0.2.24, LVGL pointer coordinates since 0.2.27): only a touch that starts in
// the side band flips a page, from the right edge leftwards "next", from the left edge
// rightwards "previous", once per touch, slow or fast, more sideways than vertical, never
// from the middle. The width follows the rotated screen.
// Edge swipe (0.2.24, back on the touchscreen triggers since 0.2.29): only a touch that
// starts in the side band flips a page, from the right edge leftwards "next", from the left
// edge rightwards "previous", once per touch, slow or fast, more sideways than vertical,
// never from the middle. Rotation follows ESPHome's pointer mapping; end() disarms.
cyd::EdgeSwipe edge;
edge.configure(32, 40);
edge.begin(6, 240, 480);
edge.configure(480, 480, 32, 40);
edge.begin(6, 240);
assert(edge.armed());
assert(edge.update(30, 242) == 0); // not far enough yet
assert(edge.update(48, 245) == -1); // 42 px inward from the left edge: previous page
assert(edge.update(90, 245) == 0); // once per touch
assert(!edge.armed());
edge.begin(474, 100, 480);
edge.begin(474, 100);
assert(edge.update(430, 104) == 1); // from the right edge: next page
edge.begin(240, 240, 480);
edge.begin(240, 240);
assert(!edge.armed());
assert(edge.update(300, 240) == 0); // started in the middle: never a page swipe
edge.begin(6, 240, 480);
edge.begin(6, 240);
assert(edge.update(50, 300) == 0); // steeper than 45 degrees: 44 px sideways against 60 down
assert(edge.update(-10, 240) == 0); // moving outward: nothing
assert(edge.update(60, 270) == -1); // 54 sideways against 30 down: a slanted thumb swipe counts
edge.begin(6, 240, 480);
edge.begin(6, 240);
edge.update(20, 250);
assert(edge.inward() == 14 && edge.sideways() == 10); // what the log reports for a swipe that ended early
edge.begin(300, 240, 320);
assert(edge.update(250, 240) == 1); // a 320 px wide (rotated or smaller) screen: right band from 288
edge.end();
assert(!edge.armed());
assert(edge.update(300, 250) == 0); // the next touch's first update, before begin(): nothing
edge.begin(240, 6, 90);
assert(edge.update(240, 60) == -1); // rotated 90: the user's left edge is the panel's top
edge.begin(240, 6, 270);
assert(edge.update(240, 60) == 1); // rotated 270: that same edge is the user's right
edge.begin(6, 240, 180);
assert(edge.update(60, 240) == 1); // upside down: the panel's left is the user's right
cyd::TouchGuard rollover;
rollover.begin(std::numeric_limits<uint32_t>::max() - 30);
assert(rollover.accept(50, 1)); // millis wraps after 49 days