Give a card's small slider the whole card and keep a quick drag on it from turning the page (0.2.81)

On a CYD the strip at the bottom of a compact card is 8 px high, so a finger landing a little above it pressed the card: the light's card opened, the light toggled, or the page-swipe gesture turned the page without a log line. The strip's click area now covers its card (slider_zone); a press that never moves forwards the card's tap or hold and shows the card pressed; the CYD's page-swipe handler and the Guition's flick handler leave a captured slider alone and log what they do; a slider commit no longer bounces against its own previous send (TouchGuard::accept_within); the CYD's edge band is 20 px because its resistive panel reports up to the bezel. App 0.2.81, firmware 0.2.68.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-18 20:08:26 +02:00
parent e928ee4404
commit b149428dcb
10 changed files with 86 additions and 25 deletions
+11 -9
View File
@@ -73,7 +73,7 @@ class TouchGuard {
void consume() { accepted_ = true; moved_ = true; }
bool accept(uint32_t now, int tile) {
if (moved_) { reject_ = MOVED; return false; }
return accept_slider(now, tile);
return accept_within(now, tile, 600);
}
// For -/+ keys and the page buttons: every clean tap counts, even the third within a second,
// so a setpoint moves several steps in one go and Next, Next, Next reaches page 4 without
@@ -87,14 +87,9 @@ class TouchGuard {
return true;
}
// Only for a slider which captured this contact and did not lose the press.
// Consume the gesture so its parent can never also turn it into a tile tap.
bool accept_slider(uint32_t now, int tile) {
if (accepted_) { reject_ = USED; return false; }
if (!long_enough(now, min_press_)) return false;
if (has_previous_ && tile == previous_tile_ && now - previous_ < 600) { reject_ = BOUNCE; return false; }
remember(now, tile);
return true;
}
// Consume the gesture so its parent can never also turn it into a tile tap. A slider sends
// once per contact, on release, so a second drag right after the first is no bounce.
bool accept_slider(uint32_t now, int tile) { return accept_within(now, tile, 0); }
// Why the last accept() refused, for the touch log; empty after a success.
std::string reason() const {
switch (reject_) {
@@ -107,6 +102,13 @@ class TouchGuard {
}
private:
static constexpr int SETTLE_SAMPLES = 4;
bool accept_within(uint32_t now, int tile, uint32_t gap) {
if (accepted_) { reject_ = USED; return false; }
if (!long_enough(now, min_press_)) return false;
if (gap && has_previous_ && tile == previous_tile_ && now - previous_ < gap) { reject_ = BOUNCE; return false; }
remember(now, tile);
return true;
}
bool long_enough(uint32_t now, uint32_t minimum) {
duration_ = now - started_;
if (duration_ < minimum) { reject_ = TOO_SHORT; return false; }
+33 -3
View File
@@ -873,9 +873,25 @@ inline void commit_slider(unsigned i,int raw){
value=std::clamp(t.minimum+std::round(value*(t.maximum-t.minimum)/t.step)*t.step,t.minimum,t.maximum);
action(d+".set_value",t.entity,"value",std::to_string(value)); }
}
// Where the finger landed on a slider, the value it had, and whether it landed beside the strip (in the tile's lower
// half that the strip claims, slider_zone) and moved: a press beside the strip that never moves is a tap on the tile.
inline lv_point_t slider_press{0,0};inline int slider_press_value=0;inline bool slider_press_beside=false,slider_press_moved=false,slider_press_held=false;
// The card a strip belongs to, for a press beside the strip that turns out to be the card's tap or hold.
inline lv_obj_t *strip_card(lv_obj_t *slider){for(auto &w:widgets)if(w.slider==slider)return w.tile;return nullptr;}
inline void slider_event(lv_event_t *e){
auto *slider=lv_event_get_target_obj(e);auto code=lv_event_get_code(e);
if(code==LV_EVENT_PRESSED){captured_slider=slider;slider_changed=false;}
if(code==LV_EVENT_PRESSED){captured_slider=slider;slider_changed=false;slider_press_moved=false;slider_press_beside=false;slider_press_held=false;
slider_press_value=lv_slider_get_value(slider);
if(auto *indev=lv_indev_active()){lv_indev_get_point(indev,&slider_press);lv_area_t a;lv_obj_get_coords(slider,&a);slider_press_beside=slider_press.x<a.x1 || slider_press.x>a.x2 || slider_press.y<a.y1 || slider_press.y>a.y2;}
// Beside the strip the card lights up as under a tap, until the finger starts dragging.
if(slider_press_beside)if(auto *card=strip_card(slider))lv_obj_add_state(card,LV_STATE_PRESSED);}
if(code==LV_EVENT_PRESSING && captured_slider==slider && !slider_press_moved){
if(auto *indev=lv_indev_active()){lv_point_t p;lv_indev_get_point(indev,&p);if(std::abs(p.x-slider_press.x)>=10 || std::abs(p.y-slider_press.y)>=10){slider_press_moved=true;
if(auto *card=strip_card(slider))lv_obj_remove_state(card,LV_STATE_PRESSED);}}}
// A finger resting beside the strip holds the card, as it would without the strip's claim on the card.
if(code==LV_EVENT_LONG_PRESSED && captured_slider==slider && slider_press_beside && !slider_press_moved && !slider_press_held){
if(auto *card=strip_card(slider)){slider_press_held=true;lv_obj_send_event(card,LV_EVENT_LONG_PRESSED,nullptr);}}
if((code==LV_EVENT_RELEASED || code==LV_EVENT_PRESS_LOST) && slider_press_beside)if(auto *card=strip_card(slider))lv_obj_remove_state(card,LV_STATE_PRESSED);
// On release LVGL sets the value once more from the last touch point: log it when that throws a
// dragged slider to one of its ends, so a stray touch sample shows up (cyd::release_jump).
if(code==LV_EVENT_VALUE_CHANGED && captured_slider==slider){
@@ -895,6 +911,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 that landed beside a tile's strip and let go without moving tapped the tile (or held it, sent above):
// the value LVGL set from that point on release goes back, and the tile's own rules decide what the tap does.
if(slider_press_beside && !slider_press_moved){
if(auto *card=strip_card(slider)){
lv_slider_set_value(slider,slider_press_value,LV_ANIM_OFF);
ESP_LOGI("slider","%s beside the strip of %s: the tile's",slider_press_held?"Hold":"Tap",model.tiles[index].entity.c_str());
if(!slider_press_held)lv_obj_send_event(card,LV_EVENT_SHORT_CLICKED,nullptr);
return;}
}
// 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);
@@ -2247,6 +2272,11 @@ inline int content_width(const Widgets &w) {
inline int content_height(const Widgets &w) {
return tile_height(w) - lv_obj_get_style_space_top(w.tile, LV_PART_MAIN) - lv_obj_get_style_space_bottom(w.tile, LV_PART_MAIN);
}
// A card's strip is thin (8 px on a CYD card), so the whole card belongs to it: a finger that lands above the strip
// still drags it. A press that never moves taps or holds the card instead (slider_event), so nothing is lost.
inline int slider_zone(const Widgets &w, int strip) {
return std::max(8, (int) lv_obj_get_style_space_top(w.tile, LV_PART_MAIN) + content_height(w) - strip);
}
inline void bind(size_t index, lv_obj_t *tile, lv_obj_t *title, lv_obj_t *value, lv_obj_t *circle, lv_obj_t *icon) {
lv_obj_update_layout(tile);
// Compact cards need room for two text lines and a separate dimmer track.
@@ -2975,7 +3005,7 @@ inline void render_full(Widgets &w,const Tile &t,bool custom,bool clock,bool sun
// The small slider becomes a strip a thumb finds at the bottom; the room above it is the button.
hide_extra(w);hide_panel(w);
int strip=big?64:40;
lv_obj_set_size(w.slider,content_w,strip);
lv_obj_set_size(w.slider,content_w,strip);lv_obj_set_ext_click_area(w.slider,slider_zone(w,strip));
slider_handle(w.slider,content_w,strip);
lv_obj_remove_flag(w.slider,LV_OBJ_FLAG_HIDDEN);
if(!lv_obj_has_state(w.slider,LV_STATE_PRESSED) && lv_slider_get_value(w.slider)!=slider_value(t))lv_slider_set_value(w.slider,slider_value(t),LV_ANIM_OFF);
@@ -3133,7 +3163,7 @@ inline void render_slot(size_t slot) {
lv_obj_set_pos(w.unit,content_w-chevron_w,std::max(0,(header_height-chevron_w)/2));lv_obj_set_size(w.unit,chevron_w,chevron_w);
lv_obj_remove_flag(w.unit,LV_OBJ_FLAG_HIDDEN);
}else lv_obj_add_flag(w.unit,LV_OBJ_FLAG_HIDDEN);
lv_obj_set_size(w.slider,content_w,slider_height);
lv_obj_set_size(w.slider,content_w,slider_height);lv_obj_set_ext_click_area(w.slider,slider_zone(w,slider_height));
slider_handle(w.slider,content_w,slider_height);
if(mini){lv_obj_remove_flag(w.slider,LV_OBJ_FLAG_HIDDEN);if(!lv_obj_has_state(w.slider,LV_STATE_PRESSED) && lv_slider_get_value(w.slider)!=slider_value(t))lv_slider_set_value(w.slider,slider_value(t),LV_ANIM_OFF);}
else lv_obj_add_flag(w.slider,LV_OBJ_FLAG_HIDDEN);