Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/gamepads/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ flutter:
default_package: gamepads_darwin
windows:
default_package: gamepads_windows
web:
default_package: gamepads_web

environment:
sdk: ">=3.9.0 <4.0.0"
Expand All @@ -32,6 +34,7 @@ dependencies:
gamepads_linux: ^0.1.1+3
gamepads_platform_interface: ^0.1.2+1
gamepads_windows: ^0.1.4+1
gamepads_web: ^0.1.0

dev_dependencies:
flame_lint: ^1.4.1
Expand Down
29 changes: 29 additions & 0 deletions packages/gamepads_web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/
1 change: 1 addition & 0 deletions packages/gamepads_web/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
../../LICENSE
1 change: 1 addition & 0 deletions packages/gamepads_web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
../../README.md
1 change: 1 addition & 0 deletions packages/gamepads_web/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:flame_lint/analysis_options.yaml
131 changes: 131 additions & 0 deletions packages/gamepads_web/lib/gamepads_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import 'dart:async';
import 'dart:js_interop';

import 'package:flutter/foundation.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:gamepads_platform_interface/api/gamepad_controller.dart';
import 'package:gamepads_platform_interface/api/gamepad_event.dart';
import 'package:gamepads_platform_interface/gamepads_platform_interface.dart';
import 'package:gamepads_web/gamepad_detector.dart';
import 'package:web/web.dart' as web;

class _GamePadState {
_GamePadState(int length) {
keyStates = List<dynamic>.filled(length, null);
axesStates = List<double>.filled(4, 0);
}

List<dynamic>? keyStates;
List<double>? axesStates;
}

/// A web implementation of the GamepadsWebPlatform of the GamepadsWeb plugin.
class GamepadsWeb extends GamepadsPlatformInterface {
int _gamepadCount = 0;
Timer? _gamepadPollingTimer;

final Map<String, _GamePadState> _lastGamePadstates = {};

void updateGamepadsStatus() {
final gamepads = getGamepadList();
for (final gamepad in gamepads) {
final buttonlist = gamepad!.buttons.toDart;
final axeslist = gamepad.axes.toDart;
final gamepadId = gamepad.index.toString();
_GamePadState lastState;
if (_lastGamePadstates.containsKey(gamepadId) &&
_lastGamePadstates[gamepadId]?.keyStates?.length ==
buttonlist.length) {
lastState = _lastGamePadstates[gamepadId]!;
} else {
_lastGamePadstates[gamepadId] = _GamePadState(buttonlist.length);
lastState = _lastGamePadstates[gamepadId]!;
}
for (var i = 0; i < buttonlist.length; i++) {
if (lastState.keyStates?[i] != buttonlist[i].value) {
lastState.keyStates?[i] = buttonlist[i].value;
emitGamepadEvent(
GamepadEvent(
gamepadId: gamepadId,
timestamp: DateTime.now().millisecondsSinceEpoch,
type: KeyType.button,
key: 'button $i',
value: buttonlist[i].value,
),
);
}
}
for (var i = 0; i < 4; i++) {
if ((lastState.axesStates![i] - axeslist[i].toDartDouble).abs() >
0.03) {
lastState.axesStates?[i] = axeslist[i].toDartDouble;
emitGamepadEvent(
GamepadEvent(
gamepadId: gamepadId,
timestamp: DateTime.now().millisecondsSinceEpoch,
type: KeyType.analog,
key: 'analog $i',
value: axeslist[i].toDartDouble,
),
);
}
}
}
}

GamepadsWeb() {
web.window.addEventListener(
'gamepadconnected',
(web.Event event) {
_gamepadCount++;
if (_gamepadCount == 1) {
// The game pad state for web is not event driven. We need to
// query the game pad state by ourself.
// By default we set the query interval is 8 ms.
_gamepadPollingTimer =
Timer.periodic(const Duration(milliseconds: 8), (timer) {
updateGamepadsStatus();
});
}
}.toJS,
);

web.window.addEventListener(
'gamepaddisconnected',
(web.Event event) {
_gamepadCount--;
if (_gamepadCount == 0) {
_gamepadPollingTimer?.cancel();
}
}.toJS,
);
}

static void registerWith(Registrar registrar) {
GamepadsPlatformInterface.instance = GamepadsWeb();
}

List<GamepadController>? controllers;

@override
Future<List<GamepadController>> listGamepads() async {
controllers = getGamepads(this);
return controllers!;
}

void emitGamepadEvent(GamepadEvent event) {
_gamepadEventsStreamController.add(event);
}

final StreamController<GamepadEvent> _gamepadEventsStreamController =
StreamController<GamepadEvent>.broadcast();

@override
Stream<GamepadEvent> get gamepadEventsStream =>
_gamepadEventsStreamController.stream;

@mustCallSuper
Future<void> dispose() async {
_gamepadEventsStreamController.close();
}
}
27 changes: 27 additions & 0 deletions packages/gamepads_web/lib/src/gamepad_detector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'dart:js_interop';

import 'package:gamepads_platform_interface/api/gamepad_controller.dart';
import 'package:gamepads_platform_interface/gamepads_platform_interface.dart';
import 'package:web/web.dart';

List<GamepadController> getGamepads(GamepadsPlatformInterface plugin) {
final controllers = <GamepadController>[];
final gamepads = getGamepadList();
for (var i = 0; i < gamepads.length; i++) {
final gamepad = gamepads[i];
controllers.add(
GamepadController(
id: gamepad!.index.toString(),
name: gamepad.id,
plugin: plugin,
),
);
}
return controllers;
}

List<Gamepad?> getGamepadList() {
final gamepads = window.navigator.getGamepads().toDart;
gamepads.removeWhere((item) => item == null);
return gamepads;
}
33 changes: 33 additions & 0 deletions packages/gamepads_web/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: gamepads_web
resolution: workspace
description: Web implementation of gamepads, a Flutter plugin to handle gamepad input across multiple platforms.
version: 0.1.0
homepage: https://github.com/flame-engine/gamepads
repository: https://github.com/flame-engine/gamepads/tree/main/packages/gamepads_web

flutter:
plugin:
implements: gamepads
platforms:
web:
pluginClass: GamepadsWeb
fileName: gamepads_web.dart

environment:
sdk: '>=2.19.0 <3.0.0'
flutter: ">=3.3.0"

dependencies:
flutter:
sdk: flutter
flutter_web_plugins:
sdk: flutter
gamepads_platform_interface: ^0.1.2+1
js_interop: ^0.0.1
plugin_platform_interface: ^2.1.8
web: ^1.1.0

dev_dependencies:
flame_lint: ^0.2.0
flutter_test:
sdk: flutter
Loading