102 lines
3.4 KiB
Dart
102 lines
3.4 KiB
Dart
|
|
// Guards the two eraser modes (the "优化橡皮擦工具" work): the default point
|
||
|
|
// eraser cuts a stroke into surviving segments, while the new stroke-eraser
|
||
|
|
// mode removes the entire stroke on contact. Both are driven by the same
|
||
|
|
// stylus pass; only the eraserWholeStroke flag differs.
|
||
|
|
|
||
|
|
import 'package:flutter/gestures.dart';
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:flutter_test/flutter_test.dart';
|
||
|
|
|
||
|
|
import 'package:badnote/editor/canvas/pen_canvas.dart';
|
||
|
|
import 'package:badnote/editor/canvas/pen_stroke.dart';
|
||
|
|
|
||
|
|
void main() {
|
||
|
|
const pageSize = Size(400, 600);
|
||
|
|
|
||
|
|
// A long horizontal stroke spanning the page through the vertical center.
|
||
|
|
PenStroke longStroke() => PenStroke(
|
||
|
|
points: const [
|
||
|
|
PenPoint(0.1, 0.5, 0.5),
|
||
|
|
PenPoint(0.3, 0.5, 0.5),
|
||
|
|
PenPoint(0.5, 0.5, 0.5),
|
||
|
|
PenPoint(0.7, 0.5, 0.5),
|
||
|
|
PenPoint(0.9, 0.5, 0.5),
|
||
|
|
],
|
||
|
|
color: 0xFF000000,
|
||
|
|
width: 0.004,
|
||
|
|
kind: PenStrokeKind.pen,
|
||
|
|
);
|
||
|
|
|
||
|
|
Widget host({
|
||
|
|
required void Function(int, List<PenStroke>) onErase,
|
||
|
|
required bool wholeStroke,
|
||
|
|
}) {
|
||
|
|
final controller = TransformationController();
|
||
|
|
addTearDown(controller.dispose);
|
||
|
|
return MaterialApp(
|
||
|
|
home: Scaffold(
|
||
|
|
body: Center(
|
||
|
|
child: SizedBox(
|
||
|
|
width: pageSize.width,
|
||
|
|
height: pageSize.height,
|
||
|
|
child: PenCanvas(
|
||
|
|
pageWidget: Container(color: const Color(0xFFEEEEEE)),
|
||
|
|
pageSize: pageSize,
|
||
|
|
strokes: [longStroke()],
|
||
|
|
transformationController: controller,
|
||
|
|
tool: CanvasTool.eraser,
|
||
|
|
color: const Color(0xFF000000),
|
||
|
|
strokeWidth: 0.004,
|
||
|
|
eraserWholeStroke: wholeStroke,
|
||
|
|
onStrokeComplete: (_) {},
|
||
|
|
onEraseStroke: onErase,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Drag the eraser vertically through the page center (normalized 0.5, 0.5),
|
||
|
|
// grazing the MIDDLE of the long horizontal stroke.
|
||
|
|
Future<void> erasePass(WidgetTester tester) async {
|
||
|
|
final center = tester.getCenter(find.byType(PenCanvas));
|
||
|
|
final g = await tester.startGesture(center + const Offset(0, -15),
|
||
|
|
kind: PointerDeviceKind.stylus);
|
||
|
|
await g.moveBy(const Offset(0, 15));
|
||
|
|
await g.moveBy(const Offset(0, 15));
|
||
|
|
await g.up();
|
||
|
|
await tester.pump();
|
||
|
|
}
|
||
|
|
|
||
|
|
testWidgets('point eraser (default) splits the stroke into surviving pieces',
|
||
|
|
(tester) async {
|
||
|
|
List<PenStroke>? replacement;
|
||
|
|
await tester.pumpWidget(host(
|
||
|
|
onErase: (_, pieces) => replacement = pieces,
|
||
|
|
wholeStroke: false,
|
||
|
|
));
|
||
|
|
await erasePass(tester);
|
||
|
|
|
||
|
|
expect(replacement, isNotNull, reason: 'an erase should have fired');
|
||
|
|
// Grazing the middle leaves the two ends as surviving sub-strokes.
|
||
|
|
expect(replacement!.length, greaterThanOrEqualTo(1));
|
||
|
|
expect(replacement, isNotEmpty,
|
||
|
|
reason: 'point eraser keeps the untouched ends');
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('stroke eraser removes the entire stroke on contact',
|
||
|
|
(tester) async {
|
||
|
|
List<PenStroke>? replacement;
|
||
|
|
await tester.pumpWidget(host(
|
||
|
|
onErase: (_, pieces) => replacement = pieces,
|
||
|
|
wholeStroke: true,
|
||
|
|
));
|
||
|
|
await erasePass(tester);
|
||
|
|
|
||
|
|
expect(replacement, isNotNull, reason: 'an erase should have fired');
|
||
|
|
expect(replacement, isEmpty,
|
||
|
|
reason: 'stroke eraser deletes the whole stroke, leaving no pieces');
|
||
|
|
});
|
||
|
|
}
|