-
-
Notifications
You must be signed in to change notification settings - Fork 2
Touch Input Quick Start
A compact reference for raw touch contacts, taps, swipes, and pinch gestures in Gondwana.
- Platform Support
- The Basic Pattern
- Show a Touch Beginning
- Show a Tap
- Show a Swipe
- Show a Pinch
- Use the Unified Gesture Event
- Tune Gesture Recognition
- Convert Touch to World Coordinates
- Test Touch with a Mouse
- Pause or Stop Touch
- Cleanup
- Cheat Sheet
- Common Problems
- Further Reading
Gondwana includes touch adapters for:
| Platform | Adapter |
|---|---|
| Avalonia | AvaloniaTouchInputAdapter |
| Blazor | BlazorTouchAdapter |
| WinForms | No built-in touch adapter |
Avalonia and Blazor game hosts initialize touch automatically.
This page uses the host hook:
protected override void OnTouchAdapterInitialized()Touch input can be consumed at two levels:
Raw contacts
TouchBegan
TouchMoved
TouchEnded
Recognized gestures
Tap
Swipe
Pinch
The poller owns the built-in recognizers, so no separate recognizer construction is required.
protected override void OnTouchAdapterInitialized()
{
var touch = Engine.Input.TouchEventPoller;
if (touch is null)
return;
touch.TouchBegan += OnTouchBegan;
touch.TouchMoved += OnTouchMoved;
touch.TouchEnded += OnTouchEnded;
touch.TouchEvent += OnTouchGesture;
touch.StartMonitoringTouch();
}using Gondwana.Input.Touch;private void OnTouchBegan(
object? sender,
TouchEventArgs e)
{
Console.WriteLine(
$"Touch {e.Touch.Id} began at {e.Touch.Position}.");
}Track movement:
private void OnTouchMoved(
object? sender,
TouchEventArgs e)
{
Console.WriteLine(
$"Touch {e.Touch.Id} moved to {e.Touch.Position}.");
}Track normal endings and cancellations:
private void OnTouchEnded(
object? sender,
TouchEventArgs e)
{
if (e.Touch.Phase == TouchPhase.Cancelled)
{
CancelInteraction(e.Touch.Id);
return;
}
CompleteInteraction(e.Touch.Id);
}Use the unified gesture event:
using Gondwana.Input.Touch.Gestures;private void OnTouchGesture(
GestureEventArgs e)
{
if (!e.IsTap)
return;
Console.WriteLine(
$"Tap at {e.Tap!.Position}.");
}Or subscribe directly to the recognizer:
touch.TapRecognizer.Tapped +=
OnTapped;private void OnTapped(
object? sender,
TappedEventArgs e)
{
Console.WriteLine(
$"Touch {e.TouchId} tapped at {e.Position}.");
}private void OnTouchGesture(
GestureEventArgs e)
{
if (!e.IsSwipe)
return;
var swipe = e.Swipe!;
Console.WriteLine(
$"Swipe {swipe.Direction} " +
$"at {swipe.SpeedPixelsPerSecond:0} px/s.");
}Respond by direction:
switch (swipe.Direction)
{
case SwipeDirection.Left:
PreviousPage();
break;
case SwipeDirection.Right:
NextPage();
break;
case SwipeDirection.Up:
OpenInventory();
break;
case SwipeDirection.Down:
CloseInventory();
break;
}private void OnTouchGesture(
GestureEventArgs e)
{
if (!e.IsPinch)
return;
var pinch = e.Pinch!;
if (pinch.Phase != PinchPhase.Updated)
return;
Console.WriteLine(
$"Scale delta: {pinch.ScaleDelta:0.000}");
}Apply it to a view:
var view =
RenderSurface.Host.ViewManager.Views[0];
float targetZoom = Math.Clamp(
view.Viewport.Zoom *
(float)pinch.ScaleDelta,
view.MinZoom,
view.MaxZoom);
view.ZoomAroundScreenPoint(
Scene![0],
pinch.Center,
targetZoom,
durationSeconds: 0f);pinch.Center is the midpoint between the two contacts.
Interpretation:
ScaleDelta > 1.0 → fingers spread apart
ScaleDelta < 1.0 → fingers move together
One handler can process every gesture:
private void OnTouchGesture(
GestureEventArgs e)
{
switch (e.GestureType)
{
case GestureType.Tap:
HandleTap(e.Tap!);
break;
case GestureType.Swipe:
HandleSwipe(e.Swipe!);
break;
case GestureType.Pinch:
HandlePinch(e.Pinch!);
break;
}
}Equivalent convenience checks:
e.IsTap
e.IsSwipe
e.IsPinchDefaults:
Maximum duration: 0.3 seconds
Maximum movement: 20 pixels
Change them:
touch.TapRecognizer
.MaxTapDurationSeconds = 0.25;
touch.TapRecognizer
.MaxTapMovementPixels = 15;Defaults:
Minimum distance: 30 pixels
Minimum speed: 200 pixels/second
Change them:
touch.SwipeRecognizer
.MinimumSwipeDistancePixels = 40;
touch.SwipeRecognizer
.MinimumSwipeSpeedPixelsPerSecond = 250;No threshold setup is required. Pinch begins when exactly two contacts are active.
Touch positions are render-surface coordinates.
private void HandleTap(
TappedEventArgs tap)
{
var view =
RenderSurface.Host.ViewManager.Views[0];
var layer = Scene![0];
PointF worldPx =
view.ScreenPxToWorldPx(
layer,
tap.Position);
PointF grid =
view.ScreenPxToGrid(
layer,
tap.Position);
SelectAt(worldPx, grid);
}With multiple views, first determine which viewport contains the touch position.
Avalonia ignores mouse pointers for touch by default. This prevents duplicate mouse and touch actions.
For desktop testing, initialize the Avalonia adapter with mouse emulation:
Engine.InitializeAvaloniaTouchAdapter(
RenderSurface,
emulateMouse: true);A mouse-emulated contact uses:
Touch ID = 0
Left mouse button = contact
Do not enable mouse emulation when the same left click is already handled separately as mouse input, unless both routes are intentional.
Pause:
touch.Configuration!.IsPaused = true;Resume:
touch.Configuration!.IsPaused = false;Stop:
touch.StopMonitoringTouch();Restart:
touch.StartMonitoringTouch();Pausing or stopping clears active contact and gesture state. A contact that began before the pause will not complete a gesture after resume.
protected override void UnhookEvents()
{
if (Engine.Input.TouchEventPoller is not { } touch)
return;
touch.TouchBegan -= OnTouchBegan;
touch.TouchMoved -= OnTouchMoved;
touch.TouchEnded -= OnTouchEnded;
touch.TouchEvent -= OnTouchGesture;
}When subscribing directly to a recognizer, detach that handler too:
touch.TapRecognizer.Tapped -= OnTapped;touch.TouchBegan += OnTouchBegan;touch.TouchMoved += OnTouchMoved;touch.TouchEnded += OnTouchEnded;touch.TouchEvent += OnTouchGesture;if (e.IsTap)
{
Point p = e.Tap!.Position;
}if (e.IsSwipe)
{
SwipeDirection direction =
e.Swipe!.Direction;
}if (e.IsPinch)
{
double scale =
e.Pinch!.ScaleDelta;
}foreach (var point in touch.ActiveTouches)
{
}The platform touch adapter was not initialized.
Avalonia and Blazor hosts do this automatically. WinForms requires a custom ITouchAdapter.
Enable Avalonia mouse emulation when initializing the adapter.
Disable emulateMouse, or make one subsystem own the action.
Touch movement is throttled. Beginnings and endings are not.
Use:
touch.StartMonitoringTouch(
timeBetweenEvents: 0);when every engine-cycle movement sample is required.
It should not. Pausing resets contacts and recognizers. Confirm the game is pausing the TouchEventPoller configuration rather than only ignoring callbacks.
Thresholds are expressed in client pixels. Tune them for the target device and UI scale.
That is intentional. Pinch updates require exactly two active contacts and safely rebase when two remain.
- Home
- Make Your First Game in 30 Minutes
- Engine Architecture Overview
- Gondwana Engine Lifecycle
- Gondwana CLI Cheatsheet
- Assets Files
- Tilesheets
- Scenes and SceneLayers
- Sprites
- Views, Cameras, and Viewports
- DirectDrawing
- Game State Files
- Logging
- Movement and Controllers
- Input Handling
- Collision Detection
- Timers and Engine Timing
- Using the Effects System
- Engine Configuration