flutter / flutter/flutter

Web does not correctly identify first stylus button press

Open
#187,532 8 comments 0 reactions 1 assignee Claimed by @mdebbar View on GitHub
assigned for triage platform-web team-web
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

### Steps to reproduce

1. Create a listener that listens for onPointerUp/onPointerMove/onPointerDown
2. Get event.buttons
3. Try it out by first pressing the first stylus button
4. Touch the listener area

### Expected results

`0b11`
first 1: stylus contact
second 1: stylus first button pressed

### Actual results

`0b100000`

### Code sample

Code sample

See flutter input demo: https://github.com/CodeDoctorDE/flutter-input-demo
Also replicated in dartpad: https://dartpad.dev/266cad036f91e043252c912f636e3be8
```dart
import 'package:flutter/material.dart';

void main() {
runApp(const InputDemoApp());
}

class InputDemoApp extends StatelessWidget {
const InputDemoApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Input Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xff0f766e),
brightness: Brightness.light,
),
scaffoldBackgroundColor: const Color(0xfff4efe6),
useMaterial3: true,
),
home: const InputDemoPage(),
);
}
}

class InputDemoPage extends StatefulWidget {
const InputDemoPage({super.key});

@override
State createState() => _InputDemoPageState();
}

class _InputDemoPageState extends State {
final GlobalKey _surfaceKey = GlobalKey();
RecognizedInput? _lastInput;

void _captureInput(PointerEvent event) {
final renderObject =
_surfaceKey.currentContext?.findRenderObject() as RenderBox?;

setState(() {
_lastInput = RecognizedInput(
event: event,
localPosition:
renderObject?.globalToLocal(event.position) ?? event.localPosition,
);
});
}

@override
Widget build(BuildContext context) {
final input = _lastInput;

return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const _Header(),
const SizedBox(height: 16),
Expanded(
child: Listener(
onPointerDown: _captureInput,
onPointerMove: _captureInput,
onPointerHover: _captureInput,
onPointerUp: _captureInput,
onPointerCancel: _captureInput,
behavior: HitTestBehavior.opaque,
child: _InputSurface(
key: _surfaceKey,
input: input,
),
),
),
const SizedBox(height: 16),
_DetailsPanel(input: input),
],
),
),
),
);
}
}

class RecognizedInput {
const RecognizedInput({
required this.event,
required this.localPosition,
});

final PointerEvent event;
final Offset localPosition;
}

class _Header extends StatelessWidget {
const _Header();

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Input Demo',
style: Theme.of(context).textTheme.displaySmall?.copyWith(
color: const Color(0xff16302b),
fontWeight: FontWeight.w800,
letterSpacing: -1.2,
),
),
const SizedBox(height: 4),
Text(
'Tap, drag, hover, or use a stylus. The big amber dot is centered exactly where this app recognized the input.',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: const Color(0xff5f6f69),
),
),
],
);
}
}

class _InputSurface extends StatelessWidget {
const _InputSurface({super.key, required this.input});

final RecognizedInput? input;

static const double _dotSize = 86;

@override
Widget build(BuildContext context) {
final input = this.input;

return ClipRRect(
borderRadius: BorderRadius.circular(28),
child: DecoratedBox(
decoration: BoxDecoration(
color: const Color(0xff103c35),
borderRadius: BorderRadius.circular(28),
boxShadow: const [
BoxShadow(
color: Color(0x2910302b),
blurRadius: 24,
offset: Offset(0, 14),
),
],
),
child: Stack(
fit: StackFit.expand,
children: [
const _SurfaceBackground(),
if (input == null)
const Center(
child: _EmptyState(),
)
else ...[
Positioned(
left: input.localPosition.dx - _dotSize / 2,
top: input.localPosition.dy - _dotSize / 2,
child: const IgnorePointer(
child: _InputDot(size: _dotSize),
),
),
Positioned(
left: input.localPosition.dx - 1,
top: 0,
bottom: 0,
child: const IgnorePointer(
child: _CrosshairLine.vertical(),
),
),
Positioned(
left: 0,
right: 0,
top: input.localPosition.dy - 1,
child: const IgnorePointer(
child: _CrosshairLine.horizontal(),
),
),
Positioned(
left: 18,
top: 18,
child: IgnorePointer(
child: _CoordinateBadge(input: input),
),
),
],
],
),
),
);
}
}

class _SurfaceBackground extends StatelessWidget {
const _SurfaceBackground();

@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _GridPainter(),
child: const DecoratedBox(
decoration: BoxDecoration(
gradient: RadialGradient(
center: Alignment(-0.8, -0.9),
radius: 1.4,
colors: [
Color(0xff24786d),
Color(0xff103c35),
Color(0xff09241f),
],
),
),
),
);
}
}

class _GridPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withValues(alpha: 0.07)
..strokeWidth = 1;

const spacing = 32.0;

for (var x = 0.0; x <= size.width; x += spacing) {
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
}

for (var y = 0.0; y <= size.height; y += spacing) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
}
}

@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

class _EmptyState extends StatelessWidget {
const _EmptyState();

@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 96,
height: 96,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white.withValues(alpha: 0.34),
width: 2,
),
),
child: Icon(
Icons.touch_app_rounded,
color: Colors.white.withValues(alpha: 0.72),
size: 44,
),
),
const SizedBox(height: 18),
Text(
'No input recognized yet',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 6),
Text(
'Interact anywhere in this panel.',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.white.withValues(alpha: 0.72),
),
),
],
);
}
}

class _InputDot extends StatelessWidget {
const _InputDot({required this.size});

final double size;

@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xffffb020),
border: Border.all(color: Colors.white, width: 5),
boxShadow: [
BoxShadow(
color: const Color(0xffffb020).withValues(alpha: 0.55),
blurRadius: 34,
spreadRadius: 8,
),
const BoxShadow(
color: Color(0x66000000),
blurRadius: 16,
offset: Offset(0, 8),
),
],
),
child: Center(
child: Container(
width: 12,
height: 12,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Color(0xff103c35),
),
),
),
);
}
}

class _CrosshairLine extends StatelessWidget {
const _CrosshairLine.vertical()
: width = 2,
height = double.infinity;

const _CrosshairLine.horizontal()
: width = double.infinity,
height = 2;

final double width;
final double height;

@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
color: Colors.white.withValues(alpha: 0.2),
);
}
}

class _CoordinateBadge extends StatelessWidget {
const _CoordinateBadge({required this.input});

final RecognizedInput input;

@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.92),
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
child: Text(
'recognized: ${_formatOffset(input.localPosition)}',
style: const TextStyle(
color: Color(0xff16302b),
fontFeatures: [FontFeature.tabularFigures()],
fontWeight: FontWeight.w800,
),
),
),
);
}
}

class _DetailsPanel extends StatelessWidget {
const _DetailsPanel({required this.input});

final RecognizedInput? input;

@override
Widget build(BuildContext context) {
final input = this.input;

return Card(
elevation: 0,
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)),
child: Padding(
padding: const EdgeInsets.all(18),
child: input == null
? const Text(
'Waiting for a pointer event...',
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xff6b756f),
fontWeight: FontWeight.w700,
),
)
: Wrap(
spacing: 10,
runSpacing: 10,
children: [
_InfoChip('Event', input.event.runtimeType.toString()),
_InfoChip('Kind', input.event.kind.name),
_InfoChip('Pointer', input.event.pointer.toString()),
_InfoChip('Local', _formatOffset(input.localPosition)),
_InfoChip('Global', _formatOffset(input.event.position)),
_InfoChip(
'Pressure', input.event.pressure.toStringAsFixed(2)),
_InfoChip(
'Buttons', '0b${input.event.buttons.toRadixString(2)}'),
],
),
),
);
}
}

class _InfoChip extends StatelessWidget {
const _InfoChip(this.label, this.value);

final String label;
final String value;

@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: const Color(0xffedf6f3),
borderRadius: BorderRadius.circular(999),
border: Border.all(color: const Color(0xffd6e8e2)),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: RichText(
text: TextSpan(
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: const Color(0xff16302b),
),
children: [
TextSpan(
text: '$label: ',
style: const TextStyle(fontWeight: FontWeight.w800),
),
TextSpan(text: value),
],
),
),
),
);
}
}

String _formatOffset(Offset offset) {
return '(${offset.dx.toStringAsFixed(1)}, ${offset.dy.toStringAsFixed(1)})';
}
```

### Screenshots or Video

Screenshots / Video demonstration

Image

### Logs

Logs

```console
flutter run -d edge
Launching lib\main.dart on Edge in debug mode...
Waiting for connection from debug service on E 35,4s /

Flutter run key commands.
r Hot reload.
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

This app is linked to the debug service: ws://127.0.0.1:49678/qCbq2hHDaLM=/ws
Debug service listening on ws://127.0.0.1:49678/qCbq2hHDaLM=/ws
A Dart VM Service on Edge is available at: http://127.0.0.1:49678/qCbq2hHDaLM=
The Flutter DevTools debugger and profiler on Edge is available at:
http://127.0.0.1:49678/qCbq2hHDaLM=/devtools/?uri=ws://127.0.0.1:49678/qCbq2hHDaLM=/ws
Starting application from main method in: org-dartlang-app:/web_entrypoint.dart.
```

### Flutter Doctor output

Doctor output

```console[√] Flutter (Channel stable, 3.44.1, on Microsoft Windows [Version 10.0.26200.8524], locale
de-DE) [1.414ms]
• Flutter version 3.44.1 on channel stable at C:\tools\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 924134a44c (5 days ago), 2026-05-29 12:13:22 -0400
• Engine revision c416acfeb8
• Dart version 3.12.1
• DevTools version 2.57.0
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop,
enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets,
enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging,
enable-uiscene-migration

[√] Windows Version (Windows 11 or higher, 25H2, 2009) [5,7s]

[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [9,9s]
• Android SDK at C:\Users\wusel\AppData\Local\Android\sdk
• Emulator version 36.5.11.0 (build_id 15261927) (CL:N/A)
• Platform android-36, build-tools 36.1.0
• Java binary at: C:\Users\wusel\AppData\Local\Programs\Android Studio\jbr\bin\java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 21.0.10+-14961533-b1163.108)
• All Android licenses accepted.

[X] Chrome - develop for the web (Cannot find Chrome executable at
.\Google\Chrome\Application\chrome.exe) [424ms]
! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[√] Visual Studio - develop Windows apps (Visual Studio Build Tools 2022 17.14.33 (May 2026))
[417ms]
• Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools
• Visual Studio Build Tools 2022 version 17.14.37314.3
• Windows 10 SDK version 10.0.26100.0

[√] Connected device (2 available) [1.003ms]
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version
10.0.26200.8524]
• Edge (web) • edge • web-javascript • Microsoft Edge 148.0.3967.96

[√] Network resources [1.451ms]
• All expected network resources are available.

! Doctor found issues in 1 category.
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.