gesture feature initial
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mozilla_components/src/extensions/subject.dart';
|
||||
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
/// Service for native touch-gesture recognition.
|
||||
///
|
||||
/// Pushes the user's gesture configuration to the native recognizer via
|
||||
/// [setGestureConfig] and exposes a stream of recognized gesture keys. The
|
||||
/// native side is purely observational (it never consumes touch input for
|
||||
/// gestures), so recognized events fire on touch release for multi-stroke
|
||||
/// gestures only.
|
||||
///
|
||||
/// A gesture key is the canonical encoding shared with native: an optional
|
||||
/// start-position prefix (`L:`/`R:`/`T:`/`B:`/`W:`/`E:`), an optional
|
||||
/// finger-count prefix (`2:` …), and the dash-joined directions (`U`/`D`/`L`/
|
||||
/// `R`), e.g. `R:2:D-L` or `D-R`.
|
||||
class GeckoGestureService extends GeckoGestureEvents {
|
||||
final GeckoGestureApi _api;
|
||||
|
||||
final _recognizedGestureSubject = PublishSubject<String>();
|
||||
|
||||
final _gestureProgressSubject = BehaviorSubject<String?>.seeded(null);
|
||||
|
||||
/// Stream of recognized gesture keys.
|
||||
///
|
||||
/// Emits the canonical key (e.g. `D-R`) each time the native recognizer
|
||||
/// matches a configured gesture.
|
||||
Stream<String> get recognizedGestures => _recognizedGestureSubject.stream;
|
||||
|
||||
/// Stream of the in-progress stroke for the live feedback overlay.
|
||||
///
|
||||
/// Emits the current partial canonical key (e.g. `R:D`) each time a new
|
||||
/// arrow is drawn, and `null` when the stroke ends (release, cancel or idle
|
||||
/// timeout). Consumers render the overlay while non-null.
|
||||
Stream<String?> get gestureProgress => _gestureProgressSubject.stream;
|
||||
|
||||
/// Creates a new gesture service.
|
||||
///
|
||||
/// Call [setUp] to register the event handlers after construction.
|
||||
GeckoGestureService({
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) : _api = GeckoGestureApi(
|
||||
binaryMessenger: binaryMessenger,
|
||||
messageChannelSuffix: messageChannelSuffix,
|
||||
);
|
||||
|
||||
/// Sets up the service to receive events from native.
|
||||
///
|
||||
/// Must be called before events will be received.
|
||||
void setUp({
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) {
|
||||
GeckoGestureEvents.setUp(
|
||||
this,
|
||||
binaryMessenger: binaryMessenger,
|
||||
messageChannelSuffix: messageChannelSuffix,
|
||||
);
|
||||
}
|
||||
|
||||
/// Pushes the gesture-recognition configuration to native.
|
||||
///
|
||||
/// Call whenever the user's gesture settings change.
|
||||
Future<void> setGestureConfig(GestureConfig config) async {
|
||||
await _api.setGestureConfig(config);
|
||||
}
|
||||
|
||||
// GeckoGestureEvents implementation
|
||||
|
||||
@override
|
||||
void onGestureRecognized(int sequence, String gestureKey) {
|
||||
_recognizedGestureSubject.addWhenMoreRecent(sequence, null, gestureKey);
|
||||
}
|
||||
|
||||
@override
|
||||
void onGestureProgress(int sequence, String partialKey) {
|
||||
_gestureProgressSubject.addWhenMoreRecent(sequence, null, partialKey);
|
||||
}
|
||||
|
||||
@override
|
||||
void onGestureReset(int sequence) {
|
||||
_gestureProgressSubject.addWhenMoreRecent(sequence, null, null);
|
||||
}
|
||||
|
||||
/// Disposes the service and closes all streams.
|
||||
Future<void> dispose() async {
|
||||
await _recognizedGestureSubject.close();
|
||||
await _gestureProgressSubject.close();
|
||||
}
|
||||
}
|
||||
@@ -6098,6 +6098,87 @@ class SandboxCaptureEntry {
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
/// Configuration for native touch-gesture recognition.
|
||||
///
|
||||
/// Pushed from Dart whenever the user's gesture settings change. Native
|
||||
/// recognition is purely observational: it assembles a canonical stroke key
|
||||
/// (start-position prefix + finger-count prefix + dash-joined directions, e.g.
|
||||
/// `R:2:D-L`) and only emits when that key matches an entry in
|
||||
/// [activeGestureKeys]. Strokes that do not match are ignored, so normal
|
||||
/// scrolling, tapping and pinch-zoom are never affected.
|
||||
class GestureConfig {
|
||||
GestureConfig({
|
||||
required this.enabled,
|
||||
required this.strokeSize,
|
||||
required this.timeoutMs,
|
||||
required this.maxFingers,
|
||||
required this.activeGestureKeys,
|
||||
});
|
||||
|
||||
bool enabled;
|
||||
|
||||
/// Base stroke length in logical pixels, scaled at runtime by
|
||||
/// `min(viewWidth, viewHeight) / 320` to match the reference gesture add-on.
|
||||
int strokeSize;
|
||||
|
||||
/// Milliseconds of inactivity after which an in-progress gesture is
|
||||
/// discarded.
|
||||
int timeoutMs;
|
||||
|
||||
/// Maximum number of simultaneous pointers a gesture may use.
|
||||
int maxFingers;
|
||||
|
||||
/// Canonical keys that currently have an action bound, e.g. `D-R`,
|
||||
/// `R:2:D-L`. Native only emits [GeckoGestureEvents.onGestureRecognized]
|
||||
/// when an assembled stroke matches one of these.
|
||||
List<String> activeGestureKeys;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[
|
||||
enabled,
|
||||
strokeSize,
|
||||
timeoutMs,
|
||||
maxFingers,
|
||||
activeGestureKeys,
|
||||
];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static GestureConfig decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return GestureConfig(
|
||||
enabled: result[0]! as bool,
|
||||
strokeSize: result[1]! as int,
|
||||
timeoutMs: result[2]! as int,
|
||||
maxFingers: result[3]! as int,
|
||||
activeGestureKeys: (result[4]! as List<Object?>).cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! GestureConfig || other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(enabled, other.enabled) &&
|
||||
_deepEquals(strokeSize, other.strokeSize) &&
|
||||
_deepEquals(timeoutMs, other.timeoutMs) &&
|
||||
_deepEquals(maxFingers, other.maxFingers) &&
|
||||
_deepEquals(activeGestureKeys, other.activeGestureKeys);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
}
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
@@ -6474,6 +6555,9 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
} else if (value is SandboxCaptureEntry) {
|
||||
buffer.putUint8(251);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is GestureConfig) {
|
||||
buffer.putUint8(252);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
@@ -6769,6 +6853,8 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
return PwaManifest.decode(readValue(buffer)!);
|
||||
case 251:
|
||||
return SandboxCaptureEntry.decode(readValue(buffer)!);
|
||||
case 252:
|
||||
return GestureConfig.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
@@ -12745,3 +12831,153 @@ abstract class SandboxCaptureHostEvents {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dart → Kotlin. Pushes the current gesture-recognition configuration.
|
||||
class GeckoGestureApi {
|
||||
/// Constructor for [GeckoGestureApi]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
GeckoGestureApi({
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) : pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
|
||||
final String pigeonVar_messageChannelSuffix;
|
||||
|
||||
Future<void> setGestureConfig(GestureConfig config) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoGestureApi.setGestureConfig$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
|
||||
<Object?>[config],
|
||||
);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
_extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Kotlin → Dart. Emitted when an assembled touch stroke matches a configured
|
||||
/// gesture key.
|
||||
abstract class GeckoGestureEvents {
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
|
||||
/// [sequence] Event sequence number for ordering.
|
||||
/// [gestureKey] Canonical key of the recognized gesture, e.g. `D-R`.
|
||||
void onGestureRecognized(int sequence, String gestureKey);
|
||||
|
||||
/// Emitted while a stroke is being drawn, each time a new direction arrow is
|
||||
/// appended. Drives the live feedback overlay.
|
||||
///
|
||||
/// [sequence] Event sequence number for ordering.
|
||||
/// [partialKey] Current partial canonical key including start/finger
|
||||
/// prefixes, e.g. `R:D`.
|
||||
void onGestureProgress(int sequence, String partialKey);
|
||||
|
||||
/// Emitted when an in-progress stroke ends (release, cancel or idle timeout)
|
||||
/// so the live feedback overlay can be hidden.
|
||||
///
|
||||
/// [sequence] Event sequence number for ordering.
|
||||
void onGestureReset(int sequence);
|
||||
|
||||
static void setUp(
|
||||
GeckoGestureEvents? api, {
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoGestureEvents.onGestureRecognized$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
final List<Object?> args = message! as List<Object?>;
|
||||
final int arg_sequence = args[0]! as int;
|
||||
final String arg_gestureKey = args[1]! as String;
|
||||
try {
|
||||
api.onGestureRecognized(arg_sequence, arg_gestureKey);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoGestureEvents.onGestureProgress$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
final List<Object?> args = message! as List<Object?>;
|
||||
final int arg_sequence = args[0]! as int;
|
||||
final String arg_partialKey = args[1]! as String;
|
||||
try {
|
||||
api.onGestureProgress(arg_sequence, arg_partialKey);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.flutter_mozilla_components.GeckoGestureEvents.onGestureReset$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger,
|
||||
);
|
||||
if (api == null) {
|
||||
pigeonVar_channel.setMessageHandler(null);
|
||||
} else {
|
||||
pigeonVar_channel.setMessageHandler((Object? message) async {
|
||||
final List<Object?> args = message! as List<Object?>;
|
||||
final int arg_sequence = args[0]! as int;
|
||||
try {
|
||||
api.onGestureReset(arg_sequence);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user