Patch SDLSurface to allow touch to be intercepted by python application
This pull request adds a feature to intercept touch events in the SDLSurface class for both the SDL2 and SDL3 bootstraps. It introduces a mechanism to set a custom OnInterceptTouchListener, allowing touch events to be handled before further processing.
Touch Event Handling Enhancements
-
SDL2 Bootstrap (
pythonforandroid/bootstraps/sdl2/build/src/patches/SDLSurface.java.patch)- Introduces an
OnInterceptTouchListenerinterface and associated methods (setInterceptTouchListenerandonTouch). - Allows a custom listener to intercept touch events. If the listener handles the event, subsequent processing is skipped.
- Introduces an
-
SDL3 Bootstrap (
pythonforandroid/bootstraps/sdl3/build/src/patches/SDLSurface.java.patch)- Implements the same
OnInterceptTouchListenerinterface and methods as SDL2, ensuring consistency. - Adds documentation clarifying that touch interception enables handling by the Python application.
- Implements the same
This feature is especially useful for forwarding touch events to Android native widgets rendered behind a Kivy widget, for example, when these metadata settings are applied:
surface.transparent = 1
surface.depth = 16
android.background_color = 0
and a native widget is inserted behind the Kivy widget:
class TouchListener(PythonJavaClass):
__javacontext__ = 'app'
__javainterfaces__ = [
'org/libsdl/app/SDLSurface$OnInterceptTouchListener']
def __init__(self, listener):
self.listener = listener
@java_method('(Landroid/view/MotionEvent;)Z')
def onTouch(self, event):
x = event.getX(0)
y = event.getY(0)
return self.listener(x, y)
def _on_touch_listener(self, x, y):
# invert Y !
y = Window.height - y
# x, y are in Window coordinate. Try to select the widget under the
# touch.
me = None
final_widget = None
for child in reversed(Window.children):
widget = self._pick(child, x, y)
if not widget:
continue
if self is widget:
me = widget
else:
final_widget = widget
if self is me and final_widget is None:
return True
self._listener = TouchListener(self._on_touch_listener)
parent = cast(LinearLayout, PythonActivity.mSurface.getParent())
parent.addView(view, 0, LayoutParams(w, h))
PythonActivity.mSurface.setInterceptTouchListener(self._listener)
A practical example is the kivy-gmap project, which uses Android's native Google MapView behind a Kivy widget.
This capability was also present in older p4a toolchains. See this legacy implementation for reference.
https://github.com/user-attachments/assets/5841823a-20f3-4070-997a-8be899f4bbd2