Unverified Commit bb65333c authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

InteractiveViewer scaleFactor (#95224)

New parameter for InteractiveViewer: scaleFactor, which allows control over the speed of zooming.
parent f1a134c5
......@@ -72,6 +72,7 @@ class InteractiveViewer extends StatefulWidget {
this.onInteractionUpdate,
this.panEnabled = true,
this.scaleEnabled = true,
this.scaleFactor = 200.0,
this.transformationController,
required Widget this.child,
}) : assert(alignPanAxis != null),
......@@ -118,6 +119,7 @@ class InteractiveViewer extends StatefulWidget {
this.onInteractionUpdate,
this.panEnabled = true,
this.scaleEnabled = true,
this.scaleFactor = 200.0,
this.transformationController,
required InteractiveViewerWidgetBuilder this.builder,
}) : assert(alignPanAxis != null),
......@@ -249,6 +251,22 @@ class InteractiveViewer extends StatefulWidget {
/// * [panEnabled], which is similar but for panning.
final bool scaleEnabled;
/// Determines the amount of scale to be performed per pointer scroll.
///
/// Defaults to 200.0, which was arbitrarily chosen to feel natural for most
/// trackpads and mousewheels on all supported platforms.
///
/// Increasing this value above the default causes scaling to feel slower,
/// while decreasing it causes scaling to feel faster.
///
/// The amount of scale is calculated as the exponential function of the
/// [PointerScrollEvent.scrollDelta] to [scaleFactor] ratio. In the Flutter
/// engine, the mousewheel [PointerScrollEvent.scrollDelta] is hardcoded to 20
/// per scroll, while a trackpad scroll can be any amount.
///
/// Affects only pointer device scrolling, not pinch to zoom.
final double scaleFactor;
/// The maximum allowed scale.
///
/// The scale will be clamped between this and [minScale] inclusively.
......@@ -881,12 +899,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
localFocalPoint: event.localPosition,
),
);
// In the Flutter engine, the mousewheel scrollDelta is hardcoded to 20
// per scroll, while a trackpad scroll can be any amount. The calculation
// for scaleChange here was arbitrarily chosen to feel natural for both
// trackpads and mousewheels on all platforms.
final double scaleChange = math.exp(-event.scrollDelta.dy / 200);
final double scaleChange = math.exp(-event.scrollDelta.dy / widget.scaleFactor);
if (!_gestureIsSupported(_GestureType.scale)) {
widget.onInteractionUpdate?.call(ScaleUpdateDetails(
......
......@@ -1311,6 +1311,86 @@ void main() {
expect(find.byType(LayoutBuilder), findsOneWidget);
});
testWidgets('scaleFactor', (WidgetTester tester) async {
const double scrollAmount = 30.0;
final TransformationController transformationController = TransformationController();
Future<void> pumpScaleFactor(double scaleFactor) {
return tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: InteractiveViewer(
boundaryMargin: const EdgeInsets.all(double.infinity),
transformationController: transformationController,
scaleFactor: scaleFactor,
child: const SizedBox(width: 200.0, height: 200.0),
),
),
),
),
);
}
// Start with the default scaleFactor.
await pumpScaleFactor(200.0);
expect(transformationController.value, equals(Matrix4.identity()));
// Zoom out. The scale decreases.
final Offset center = tester.getCenter(find.byType(InteractiveViewer));
await scrollAt(center, tester, const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();
final double scaleZoomedOut = transformationController.value.getMaxScaleOnAxis();
expect(scaleZoomedOut, lessThan(1.0));
// Zoom in. The scale increases.
await scrollAt(center, tester, const Offset(0.0, -scrollAmount));
await tester.pumpAndSettle();
final double scaleZoomedIn = transformationController.value.getMaxScaleOnAxis();
expect(scaleZoomedIn, greaterThan(scaleZoomedOut));
// Reset and decrease the scaleFactor below the default, so that scaling
// will happen more quickly.
transformationController.value = Matrix4.identity();
await pumpScaleFactor(100.0);
// Zoom out. The scale decreases more quickly than with the default
// (higher) scaleFactor.
await scrollAt(center, tester, const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();
final double scaleLowZoomedOut = transformationController.value.getMaxScaleOnAxis();
expect(scaleLowZoomedOut, lessThan(1.0));
expect(scaleLowZoomedOut, lessThan(scaleZoomedOut));
// Zoom in. The scale increases more quickly than with the default
// (higher) scaleFactor.
await scrollAt(center, tester, const Offset(0.0, -scrollAmount));
await tester.pumpAndSettle();
final double scaleLowZoomedIn = transformationController.value.getMaxScaleOnAxis();
expect(scaleLowZoomedIn, greaterThan(scaleLowZoomedOut));
expect(scaleLowZoomedIn - scaleLowZoomedOut, greaterThan(scaleZoomedIn - scaleZoomedOut));
// Reset and increase the scaleFactor above the default.
transformationController.value = Matrix4.identity();
await pumpScaleFactor(400.0);
// Zoom out. The scale decreases, but not by as much as with the default
// (higher) scaleFactor.
await scrollAt(center, tester, const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();
final double scaleHighZoomedOut = transformationController.value.getMaxScaleOnAxis();
expect(scaleHighZoomedOut, lessThan(1.0));
expect(scaleHighZoomedOut, greaterThan(scaleZoomedOut));
// Zoom in. The scale increases, but not by as much as with the default
// (higher) scaleFactor.
await scrollAt(center, tester, const Offset(0.0, -scrollAmount));
await tester.pumpAndSettle();
final double scaleHighZoomedIn = transformationController.value.getMaxScaleOnAxis();
expect(scaleHighZoomedIn, greaterThan(scaleHighZoomedOut));
expect(scaleHighZoomedIn - scaleHighZoomedOut, lessThan(scaleZoomedIn - scaleZoomedOut));
});
});
group('getNearestPointOnLine', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment