Skip to content

Conversation

kengoon
Copy link
Contributor

@kengoon kengoon commented May 15, 2025

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 OnInterceptTouchListener interface and associated methods (setInterceptTouchListener and onTouch).
    • Allows a custom listener to intercept touch events. If the listener handles the event, subsequent processing is skipped.
  • SDL3 Bootstrap (pythonforandroid/bootstraps/sdl3/build/src/patches/SDLSurface.java.patch)

    • Implements the same OnInterceptTouchListener interface and methods as SDL2, ensuring consistency.
    • Adds documentation clarifying that touch interception enables handling by the Python application.

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.

Screen_Recording_20250516_010309.mp4

@kuzeyron kuzeyron added the core-providers Core code that's not a recipe label May 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-providers Core code that's not a recipe
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants