Tutorial 49: Touch Input and Gestures
What you’ll learn
- Polling
TouchPanel.GetState()and reading aTouchLocation. - The
TouchLocationStatelifecycle of a single touch. - Enabling gesture recognition and reading a
GestureSample. - Drag and pinch-to-zoom as worked examples.
Before you start — Tutorial 11: Handling Mouse Input — touch follows the same per-frame polling shape as the mouse. Touch input only produces real data on a touch-capable target: Android, or a touch-enabled desktop or web build.
Platform notes. This example assumes the default SDL3 platform implementation; platform selection is independent of the graphics renderer. All ten XNA gesture types are detected. On desktop Linux and Windows, SDL forwards touch-screen events from tablets and many laptop touchscreens. Android touch should be validated on a device. iOS support in alpha.1 is a narrow SDL_RENDERER build/link and one-frame simulator smoke path, not broad device validation; tvOS remains unsupported.
TouchPanel.GetState()
Returns a TouchCollection containing all currently active touch contacts.
#include "Microsoft/Xna/Framework/Input/Touch/TouchPanel.hpp"
#include "Microsoft/Xna/Framework/Input/Touch/TouchCollection.hpp"
#include "Microsoft/Xna/Framework/Input/Touch/TouchLocation.hpp"
using namespace Microsoft::Xna::Framework::Input::Touch;
void Update(GameTime& gameTime) override {
TouchCollection touches = TouchPanel::GetState();
for (const TouchLocation& touch : touches) {
Vector2 pos = touch.Position; // screen coordinates (pixels)
int id = touch.Id; // unique per contact (finger)
switch (touch.getStateProperty()) {
case TouchLocationState::Pressed:
onTouchDown(id, pos);
break;
case TouchLocationState::Moved:
onTouchMove(id, pos);
break;
case TouchLocationState::Released:
onTouchUp(id, pos);
break;
case TouchLocationState::Invalid:
// Contact lost or prediction artefact — ignore
break;
}
}
}
TouchLocation struct
// TouchLocation fields (read-only)
struct TouchLocation {
int Id; // Unique ID per contact; stable while finger is down
TouchLocationState State; // Pressed / Moved / Released / Invalid
Vector2 Position; // Screen position in pixels
float Pressure; // 0.0 – 1.0 (hardware support varies)
// Try to get the state of this contact from the previous frame
bool TryGetPreviousLocation(TouchLocation& previousLocation) const;
};
TouchLocationState enum
enum class TouchLocationState {
Invalid, // lost or synthetic — do not use
Moved, // finger is held and has moved since last frame
Pressed, // finger just made contact
Released, // finger lifted off the screen
};
Enabling gesture recognition
// Call before the first Update (e.g. in Initialize())
TouchPanel::setEnabledGesturesProperty(
GestureType::Tap |
GestureType::DoubleTap |
GestureType::FreeDrag |
GestureType::Pinch |
GestureType::PinchComplete);
// In Update(): drain the gesture queue
while (TouchPanel::getIsGestureAvailableProperty()) {
GestureSample gesture = TouchPanel::ReadGesture();
processGesture(gesture);
}
GestureType enum (selected values)
| GestureType | Description |
|---|---|
Tap | Quick press and release |
DoubleTap | Two rapid taps |
Hold | Finger held in place |
FreeDrag | Single-finger pan (any direction) |
HorizontalDrag | Horizontal-only pan |
VerticalDrag | Vertical-only pan |
DragComplete | Fired once when drag ends |
Flick | Fast swipe; GestureSample.Delta gives velocity |
Pinch | Two-finger spread/contract |
PinchComplete | Fired once when pinch ends |
GestureSample fields
struct GestureSample {
GestureType GestureType; // which gesture was detected
TimeSpan Timestamp; // when the gesture was recognised
Vector2 Position; // primary contact position (finger 1)
Vector2 Position2; // secondary contact position (finger 2, for Pinch)
Vector2 Delta; // movement delta for this frame (drag, flick velocity)
Vector2 Delta2; // secondary delta (for Pinch)
};
Code example: drag gesture and pinch-to-zoom
class TouchDemo final : public Game {
public:
TouchDemo() : graphics_(this) {}
protected:
void Initialize() override {
Game::Initialize();
// Enable the gestures we need
TouchPanel::setEnabledGesturesProperty(
GestureType::FreeDrag |
GestureType::DragComplete|
GestureType::Pinch |
GestureType::PinchComplete);
cameraOffset_ = Vector2::Zero;
zoom_ = 1.0f;
}
void Update(GameTime& gameTime) override {
// 1. Raw touch for individual finger tracking
TouchCollection touches = TouchPanel::GetState();
for (const auto& t : touches) {
if (t.getStateProperty() == TouchLocationState::Pressed)
lastTouchCount_ = static_cast<int>(touches.getCountProperty());
}
// 2. High-level gestures
while (TouchPanel::getIsGestureAvailableProperty()) {
GestureSample gs = TouchPanel::ReadGesture();
switch (gs.getGestureTypeProperty()) {
case GestureType::FreeDrag:
// Pan the "camera" offset
cameraOffset_ = cameraOffset_ + gs.getDeltaProperty();
break;
case GestureType::DragComplete:
// Could apply momentum / deceleration here
break;
case GestureType::Pinch: {
// Compute pinch scale from current vs previous finger distance
Vector2 prevDiff = gs.getPositionProperty() - gs.getPosition2Property();
Vector2 currDiff = (gs.getPositionProperty() + gs.getDeltaProperty())
- (gs.getPosition2Property() + gs.getDelta2Property());
float prevDist = prevDiff.Length();
float currDist = currDiff.Length();
if (prevDist > 1.0f) {
float scale = currDist / prevDist;
zoom_ = MathHelper::Clamp(zoom_ * scale, 0.2f, 5.0f);
}
break;
}
case GestureType::PinchComplete:
// Snap zoom to nearest 0.5 step (optional)
zoom_ = std::round(zoom_ * 2.0f) / 2.0f;
break;
default:
break;
}
}
}
void Draw(const GameTime&) override {
auto& gd = getGraphicsDeviceProperty();
gd.Clear(Color::CornflowerBlue);
// Apply pan + zoom via a SpriteBatch transform
Matrix transform = Matrix::CreateTranslation(cameraOffset_.X,
cameraOffset_.Y, 0)
* Matrix::CreateScale(zoom_);
spriteBatch_->Begin(SpriteSortMode::Deferred,
BlendState::AlphaBlend, nullptr, nullptr, nullptr,
nullptr, transform);
// ... draw game world ...
spriteBatch_->End();
gd.Present();
}
private:
GraphicsDeviceManager graphics_;
std::unique_ptr<SpriteBatch> spriteBatch_;
Vector2 cameraOffset_;
float zoom_ = 1.0f;
int lastTouchCount_ = 0;
};