[ltk — src/input/gesture/mod.rs, src/input/gesture/tests.rs, src/widget/handlers.rs, src/input/keyboard/dispatch.rs, src/render/image.rs, docs/architecture.md, CHANGELOG.md] Keep a tap on a button while the finger rolls inside it, treat XF86Back like Escape, and convert ARGB8888 by whole pixels
CI / build + test (push) Has been cancelled
CI / cargo audit (push) Has been cancelled

Any travel past the slop turned a press into a vertical or horizontal drag and its release skipped the button, so a thumb that rolled on a large key lost the tap. `GestureState::on_move` now stays idle while the pressed widget fires on press (the new `WidgetHandlers::has_press`) and the finger is still inside its rect; the swipe machinery takes over from the original press point once the finger leaves. Presses inside a scroll viewport are unaffected, since a list of full-width rows is scrolled by dragging on them. Two tests cover the tap that survives motion inside the button and the swipe that arms once the finger leaves it; the architecture notes and the changelog describe the rule.
The compositor now sends XF86Back for the Android back gesture, an inward swipe from either side edge. With a text input focused that keysym fell into `handle_key_default` and was swallowed, so an app never saw it. `XF86Back` now dispatches through `handle_key_escape`, so every ltk app gets the back gesture with Escape's layering: open popups, then the context menu, then a selection, then a dialog's cancel, and finally the focus drop and `on_key_with_modifiers` with the keysym, where an app can act on it.
`SoftwareCanvas::write_to_wayland_buf` swapped red and blue with a byte-by-byte loop, unrolled by hand over sixteen bytes. It now reads each pixel as a `u32`, exchanges the two channels with masks and shifts, and leaves the vectorisation to the compiler. Compositors that offer `ABGR8888` never took this path.
This commit is contained in:
2026-09-14 10:57:10 +02:00
parent 75be800390
commit 5b8ae35830
7 changed files with 52 additions and 31 deletions
+10
View File
@@ -420,6 +420,16 @@ impl<Msg: Clone> GestureState<Msg>
return MoveOutcome::Idle;
}
// A press on a button stays a tap while the finger is inside it;
// the swipe only starts once it leaves.
if let Some( w ) = self.pressed_idx.and_then( |idx| find_widget( widget_rects, idx ) )
{
if w.handlers.has_press() && w.rect.contains( pos )
{
return MoveOutcome::Idle;
}
}
// Swipe progress — locked to a single axis (see below) so a
// gesture only ever drives one of the vertical panels or the
// horizontal pager, never both.
+27
View File
@@ -487,6 +487,33 @@ fn release_on_button_emits_press_msg()
assert!( matches!( events[ 0 ], ReleaseEvent::PushMsg( Msg::Pressed ) ) );
}
#[ test ]
fn motion_inside_pressed_button_keeps_the_tap()
{
let widgets = vec![ button( 1, rect( 0.0, 0.0, 100.0, 100.0 ), Some( Msg::Pressed ), None ) ];
let mut g = GestureState::<Msg>::new();
let _ = g.on_press( pt( 50.0, 80.0 ), &widgets, &[] );
let mut offsets = HashMap::new();
let out = g.on_move( pt( 55.0, 50.0 ), &widgets, &mut offsets, &cfg_full( 800, 1200 ), false );
assert!( matches!( out, MoveOutcome::Idle ) );
assert!( !g.vertical_drag_started );
let events = g.on_release( pt( 55.0, 50.0 ), &widgets, &cfg_full( 800, 1200 ), false );
assert!( matches!( events[ 0 ], ReleaseEvent::PushMsg( Msg::Pressed ) ) );
}
#[ test ]
fn motion_leaving_pressed_button_arms_the_swipe()
{
let widgets = vec![ button( 1, rect( 0.0, 0.0, 100.0, 100.0 ), Some( Msg::Pressed ), None ) ];
let mut g = GestureState::<Msg>::new();
let _ = g.on_press( pt( 50.0, 50.0 ), &widgets, &[] );
let mut offsets = HashMap::new();
let _ = g.on_move( pt( 50.0, 120.0 ), &widgets, &mut offsets, &cfg_full( 800, 1200 ), false );
assert!( g.vertical_drag_started );
let events = g.on_release( pt( 50.0, 120.0 ), &widgets, &cfg_full( 800, 1200 ), false );
assert!( events.iter().all( |e| !matches!( e, ReleaseEvent::PushMsg( _ ) ) ) );
}
#[ test ]
fn release_on_different_widget_does_not_emit_press_msg()
{
+1 -1
View File
@@ -32,7 +32,7 @@ impl<A: App> AppData<A>
Keysym::x | Keysym::X if self.ctrl_pressed => self.handle_key_ctrl_x( focus ),
Keysym::v | Keysym::V if self.ctrl_pressed => self.handle_key_ctrl_v( focus ),
Keysym::Tab | Keysym::ISO_Left_Tab => self.handle_key_tab( focus, &event, qh ),
Keysym::Escape => self.handle_key_escape( focus, &event, qh ),
Keysym::Escape | Keysym::XF86_Back => self.handle_key_escape( focus, &event, qh ),
Keysym::space => self.handle_key_space( focus, &event ),
_ => self.handle_key_default( focus, &event ),
}