twonly-app-dependencies/hand_signature/lib/src/signature_view.dart
2025-12-26 18:50:40 +01:00

217 lines
8.6 KiB
Dart

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../signature.dart';
typedef _GestureEvent = Function(Offset position, double pressure);
/// A widget that provides a canvas for drawing hand signatures.
/// It combines [HandSignaturePaint] for rendering and [RawGestureDetector] for input handling,
/// sending gesture events to a [HandSignatureControl].
class HandSignature extends StatelessWidget {
/// The controller that manages the creation and manipulation of signature paths.
final HandSignatureControl control;
/// @Deprecated('This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing type and style.')
/// The type of signature path to draw.
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing type and style.')
final SignatureDrawType type;
/// @Deprecated('This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing color.')
/// The single color used for painting the signature.
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing color.')
final Color color;
/// @Deprecated('This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the minimal stroke width.')
/// The minimal stroke width of the signature path.
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the minimal stroke width.')
final double width;
/// @Deprecated('This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the maximal stroke width.')
/// The maximal stroke width of the signature path.
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the maximal stroke width.')
final double maxWidth;
/// The [HandSignatureDrawer] responsible for rendering the signature.
/// If `null`, a default drawer will be created based on the deprecated `type`, `color`, `width`, and `maxWidth` properties.
final HandSignatureDrawer? drawer;
/// The set of [PointerDeviceKind]s that this widget should recognize.
///
/// For example, to only accept stylus input:
/// ```dart
/// supportedDevices: {
/// PointerDeviceKind.stylus,
/// }
/// ```
/// If `null`, it accepts input from all pointer device types.
final Set<PointerDeviceKind>? supportedDevices;
/// Optional callback function invoked when a new signature path drawing starts (pointer down event).
final VoidCallback? onPointerDown;
/// Optional callback function invoked when a signature path drawing ends (pointer up or cancel event).
final VoidCallback? onPointerUp;
/// Creates a [HandSignature] widget.
///
/// [key] Controls how one widget replaces another widget in the tree.
/// [control] The [HandSignatureControl] instance to manage the signature data.
/// [type] The deprecated drawing type for the signature.
/// [color] The deprecated color for the signature.
/// [width] The deprecated minimal width for the signature.
/// [maxWidth] The deprecated maximal width for the signature.
/// [drawer] The custom drawer to use for rendering the signature.
/// [onPointerDown] Callback for when drawing starts.
/// [onPointerUp] Callback for when drawing ends.
/// [supportedDevices] The set of pointer device types to recognize.
const HandSignature({
Key? key,
required this.control,
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing type and style.')
this.type = SignatureDrawType.shape,
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the drawing color.')
this.color = Colors.black,
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the minimal stroke width.')
this.width = 1.0,
@Deprecated(
'This property is deprecated since 3.1.0. Use the `drawer` property instead to specify the maximal stroke width.')
this.maxWidth = 10.0,
this.drawer,
this.onPointerDown,
this.onPointerUp,
this.supportedDevices,
}) : super(key: key);
void _startPath(Offset point, double pressure) {
if (!control.hasActivePath) {
onPointerDown?.call();
control.startPath(point, pressure: pressure);
}
}
void _endPath(Offset point, double pressure) {
if (control.hasActivePath) {
control.closePath(pressure: pressure);
onPointerUp?.call();
}
}
@override
Widget build(BuildContext context) {
control.params = SignaturePaintParams(
color: color,
strokeWidth: width,
maxStrokeWidth: maxWidth,
);
return ClipRRect(
child: RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
_SingleGestureRecognizer:
GestureRecognizerFactoryWithHandlers<_SingleGestureRecognizer>(
() => _SingleGestureRecognizer(
debugOwner: this, supportedDevices: supportedDevices),
(instance) {
instance.onStart =
(position, pressure) => _startPath(position, pressure);
instance.onUpdate = (position, pressure) =>
control.alterPath(position, pressure: pressure);
instance.onEnd =
(position, pressure) => _endPath(position, pressure);
},
),
},
child: HandSignaturePaint(
control: control,
drawer: drawer ??
switch (type) {
SignatureDrawType.line =>
LineSignatureDrawer(color: color, width: width),
SignatureDrawType.arc => ArcSignatureDrawer(
color: color, width: width, maxWidth: maxWidth),
SignatureDrawType.shape => ShapeSignatureDrawer(
color: color, width: width, maxWidth: maxWidth),
},
onSize: control.notifyDimension,
),
),
);
}
}
/// A custom [GestureRecognizer] that processes only a single input pointer
/// for signature drawing. It extends [OneSequenceGestureRecognizer] to ensure
/// that only one gesture is recognized at a time.
class _SingleGestureRecognizer extends OneSequenceGestureRecognizer {
@override
String get debugDescription => 'single_gesture_recognizer';
/// Callback function for when a pointer starts interacting with the widget.
_GestureEvent? onStart;
/// Callback function for when a pointer moves while interacting with the widget.
_GestureEvent? onUpdate;
/// Callback function for when a pointer stops interacting with the widget.
_GestureEvent? onEnd;
/// A flag indicating whether a pointer is currently active (down).
bool pointerActive = false;
/// Creates a [_SingleGestureRecognizer].
///
/// [debugOwner] The object that is debugging this recognizer.
/// [supportedDevices] The set of [PointerDeviceKind]s that this recognizer should respond to.
/// If `null`, it defaults to all available pointer device kinds.
_SingleGestureRecognizer({
super.debugOwner,
Set<PointerDeviceKind>? supportedDevices,
}) : super(
supportedDevices:
supportedDevices ?? PointerDeviceKind.values.toSet(),
);
@override
void addAllowedPointer(PointerDownEvent event) {
// Only allow a new pointer if no other pointer is currently active.
if (pointerActive) {
return;
}
// Start tracking the pointer.
startTrackingPointer(event.pointer, event.transform);
}
@override
void handleEvent(PointerEvent event) {
// Handle different types of pointer events.
if (event is PointerMoveEvent) {
// If it's a move event, call the onUpdate callback.
onUpdate?.call(event.localPosition, event.pressure);
} else if (event is PointerDownEvent) {
// If it's a down event, set pointer as active and call onStart.
pointerActive = true;
onStart?.call(event.localPosition, event.pressure);
} else if (event is PointerUpEvent) {
// If it's an up event, set pointer as inactive and call onEnd.
pointerActive = false;
onEnd?.call(event.localPosition, event.pressure);
} else if (event is PointerCancelEvent) {
// If the pointer interaction is cancelled, set pointer as inactive and call onEnd.
pointerActive = false;
onEnd?.call(event.localPosition, event.pressure);
}
}
@override
void didStopTrackingLastPointer(int pointer) {
// No specific action needed when the last pointer stops tracking.
}
}