diff --git a/dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart b/dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart new file mode 100644 index 000000000000..1a933193ab19 --- /dev/null +++ b/dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart @@ -0,0 +1,73 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show clampDouble; + +import '../common.dart'; + +const int _kBatchSize = 100000; +const int _kNumIterations = 1000; + +void main() { + assert(false, + "Don't run benchmarks in debug mode! Use 'flutter run --release'."); + final BenchmarkResultPrinter printer = BenchmarkResultPrinter(); + + final Stopwatch watch = Stopwatch(); + { + final List clampDoubleValues = []; + for (int j = 0; j < _kNumIterations; ++j) { + double tally = 0; + watch.reset(); + watch.start(); + for (int i = 0; i < _kBatchSize; i += 1) { + tally += clampDouble(-1.0, 0.0, 1.0); + tally += clampDouble(2.0, 0.0, 1.0); + tally += clampDouble(0.0, 0.0, 1.0); + tally += clampDouble(double.nan, 0.0, 1.0); + } + watch.stop(); + clampDoubleValues.add(watch.elapsedMicroseconds.toDouble() / _kBatchSize); + if (tally < 0.0) { + print("This shouldn't happen."); + } + } + + printer.addResultStatistics( + description: 'clamp - clampDouble', + values: clampDoubleValues, + unit: 'us per iteration', + name: 'clamp_clampDouble', + ); + } + + { + final List doubleClampValues = []; + + for (int j = 0; j < _kNumIterations; ++j) { + double tally = 0; + watch.reset(); + watch.start(); + for (int i = 0; i < _kBatchSize; i += 1) { + tally += -1.0.clamp(0.0, 1.0); + tally += 2.0.clamp(0.0, 1.0); + tally += 0.0.clamp(0.0, 1.0); + tally += double.nan.clamp(0.0, 1.0); + } + watch.stop(); + doubleClampValues.add(watch.elapsedMicroseconds.toDouble() / _kBatchSize); + if (tally < 0.0) { + print("This shouldn't happen."); + } + } + + printer.addResultStatistics( + description: 'clamp - Double.clamp', + values: doubleClampValues, + unit: 'us per iteration', + name: 'clamp_Double_clamp', + ); + } + printer.printToStdout(); +} diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index 974d6bd91cbf..0bef90aeda4d 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -88,6 +88,9 @@ Future run(List arguments) async { exitWithError(['The analyze.dart script must be run with --enable-asserts.']); } + print('$clock No Double.clamp'); + await verifyNoDoubleClamp(flutterRoot); + print('$clock All tool test files end in _test.dart...'); await verifyToolTestsEndInTestDart(flutterRoot); @@ -203,6 +206,80 @@ Future run(List arguments) async { // TESTS +FeatureSet _parsingFeatureSet() => FeatureSet.fromEnableFlags2( + sdkLanguageVersion: Version.parse('2.17.0-0'), + flags: ['super-parameters']); + +_Line _getLine(ParseStringResult parseResult, int offset) { + final int lineNumber = + parseResult.lineInfo.getLocation(offset).lineNumber; + final String content = parseResult.content.substring( + parseResult.lineInfo.getOffsetOfLine(lineNumber - 1), + parseResult.lineInfo.getOffsetOfLine(lineNumber) - 1); + return _Line(lineNumber, content); +} + +class _DoubleClampVisitor extends RecursiveAstVisitor { + _DoubleClampVisitor(this.parseResult); + + final List<_Line> clamps = <_Line>[]; + final ParseStringResult parseResult; + + @override + CompilationUnit? visitMethodInvocation(MethodInvocation node) { + if (node.methodName.name == 'clamp') { + final _Line line = _getLine(parseResult, node.function.offset); + if (!line.content.contains('// ignore_clamp_double_lint')) { + clamps.add(line); + } + } + + node.visitChildren(this); + return null; + } +} + +/// Verify that we use clampDouble instead of Double.clamp for performance reasons. +/// +/// We currently can't distinguish valid uses of clamp from problematic ones so +/// if the clamp is operating on a type other than a `double` the +/// `// ignore_clamp_double_lint` comment must be added to the line where clamp is +/// invoked. +/// +/// See also: +/// * https://github.com/flutter/flutter/pull/103559 +/// * https://github.com/flutter/flutter/issues/103917 +Future verifyNoDoubleClamp(String workingDirectory) async { + final String flutterLibPath = '$workingDirectory/packages/flutter/lib'; + final Stream testFiles = + _allFiles(flutterLibPath, 'dart', minimumMatches: 100); + final List errors = []; + await for (final File file in testFiles) { + try { + final ParseStringResult parseResult = parseFile( + featureSet: _parsingFeatureSet(), + path: file.absolute.path, + ); + final _DoubleClampVisitor visitor = _DoubleClampVisitor(parseResult); + visitor.visitCompilationUnit(parseResult.unit); + for (final _Line clamp in visitor.clamps) { + errors.add('${file.path}:${clamp.line}: `clamp` method used without ignore_clamp_double_lint comment.'); + } + } catch (ex) { + // TODO(gaaclarke): There is a bug with super parameter parsing on mac so + // we skip certain files until that is fixed. + // https://github.com/dart-lang/sdk/issues/49032 + print('skipping ${file.path}: $ex'); + } + } + if (errors.isNotEmpty) { + exitWithError([ + ...errors, + '\n${bold}See: https://github.com/flutter/flutter/pull/103559', + ]); + } +} + /// Verify tool test files end in `_test.dart`. /// /// The test runner will only recognize files ending in `_test.dart` as tests to @@ -518,16 +595,16 @@ Future _verifyNoMissingLicenseForExtension( return 0; } -class _TestSkip { - _TestSkip(this.line, this.content); +class _Line { + _Line(this.line, this.content); final int line; final String content; } -Iterable<_TestSkip> _getTestSkips(File file) { +Iterable<_Line> _getTestSkips(File file) { final ParseStringResult parseResult = parseFile( - featureSet: FeatureSet.fromEnableFlags2(sdkLanguageVersion: Version.parse('2.17.0-0'), flags: ['super-parameters']), + featureSet: _parsingFeatureSet(), path: file.absolute.path, ); final _TestSkipLinesVisitor visitor = _TestSkipLinesVisitor(parseResult); @@ -536,10 +613,10 @@ Iterable<_TestSkip> _getTestSkips(File file) { } class _TestSkipLinesVisitor extends RecursiveAstVisitor { - _TestSkipLinesVisitor(this.parseResult) : skips = <_TestSkip>{}; + _TestSkipLinesVisitor(this.parseResult) : skips = <_Line>{}; final ParseStringResult parseResult; - final Set<_TestSkip> skips; + final Set<_Line> skips; static bool isTestMethod(String name) { return name.startsWith('test') || name == 'group' || name == 'expect'; @@ -550,10 +627,7 @@ class _TestSkipLinesVisitor extends RecursiveAstVisitor { if (isTestMethod(node.methodName.toString())) { for (final Expression argument in node.argumentList.arguments) { if (argument is NamedExpression && argument.name.label.name == 'skip') { - final int lineNumber = parseResult.lineInfo.getLocation(argument.beginToken.charOffset).lineNumber; - final String content = parseResult.content.substring(parseResult.lineInfo.getOffsetOfLine(lineNumber - 1), - parseResult.lineInfo.getOffsetOfLine(lineNumber) - 1); - skips.add(_TestSkip(lineNumber, content)); + skips.add(_getLine(parseResult, argument.beginToken.charOffset)); } } } @@ -571,7 +645,7 @@ Future verifySkipTestComments(String workingDirectory) async { .where((File f) => f.path.endsWith('_test.dart')); await for (final File file in testFiles) { - for (final _TestSkip skip in _getTestSkips(file)) { + for (final _Line skip in _getTestSkips(file)) { final Match? match = _skipTestCommentPattern.firstMatch(skip.content); final String? skipComment = match?.group(1); if (skipComment == null || diff --git a/dev/devicelab/lib/tasks/microbenchmarks.dart b/dev/devicelab/lib/tasks/microbenchmarks.dart index 8077bbae18cf..af83d94f7e3d 100644 --- a/dev/devicelab/lib/tasks/microbenchmarks.dart +++ b/dev/devicelab/lib/tasks/microbenchmarks.dart @@ -60,6 +60,7 @@ TaskFunction createMicrobenchmarkTask() { ...await runMicrobench('lib/language/sync_star_bench.dart'), ...await runMicrobench('lib/language/sync_star_semantics_bench.dart'), ...await runMicrobench('lib/foundation/all_elements_bench.dart'), + ...await runMicrobench('lib/foundation/clamp.dart'), ...await runMicrobench('lib/foundation/change_notifier_bench.dart'), ...await runMicrobench('lib/foundation/standard_method_codec_bench.dart'), ...await runMicrobench('lib/foundation/standard_message_codec_bench.dart'), diff --git a/packages/flutter/lib/foundation.dart b/packages/flutter/lib/foundation.dart index c01ccaaf3899..56e6064f8fe8 100644 --- a/packages/flutter/lib/foundation.dart +++ b/packages/flutter/lib/foundation.dart @@ -33,6 +33,7 @@ export 'src/foundation/diagnostics.dart'; export 'src/foundation/isolates.dart'; export 'src/foundation/key.dart'; export 'src/foundation/licenses.dart'; +export 'src/foundation/math.dart'; export 'src/foundation/node.dart'; export 'src/foundation/object.dart'; export 'src/foundation/observer_list.dart'; diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart index 0bd969444c1e..d4161b3f9d86 100644 --- a/packages/flutter/lib/src/animation/animation_controller.dart +++ b/packages/flutter/lib/src/animation/animation_controller.dart @@ -395,7 +395,7 @@ class AnimationController extends Animation } void _internalSetValue(double newValue) { - _value = newValue.clamp(lowerBound, upperBound); + _value = clampDouble(newValue, lowerBound, upperBound); if (_value == lowerBound) { _status = AnimationStatus.dismissed; } else if (_value == upperBound) { @@ -598,7 +598,7 @@ class AnimationController extends Animation stop(); if (simulationDuration == Duration.zero) { if (value != target) { - _value = target.clamp(lowerBound, upperBound); + _value = clampDouble(target, lowerBound, upperBound); notifyListeners(); } _status = (_direction == _AnimationDirection.forward) ? @@ -741,7 +741,7 @@ class AnimationController extends Animation assert(!isAnimating); _simulation = simulation; _lastElapsedDuration = Duration.zero; - _value = simulation.x(0.0).clamp(lowerBound, upperBound); + _value = clampDouble(simulation.x(0.0), lowerBound, upperBound); final TickerFuture result = _ticker!.start(); _status = (_direction == _AnimationDirection.forward) ? AnimationStatus.forward : @@ -820,7 +820,7 @@ class AnimationController extends Animation _lastElapsedDuration = elapsed; final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond; assert(elapsedInSeconds >= 0.0); - _value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound); + _value = clampDouble(_simulation!.x(elapsedInSeconds), lowerBound, upperBound); if (_simulation!.isDone(elapsedInSeconds)) { _status = (_direction == _AnimationDirection.forward) ? AnimationStatus.completed : @@ -855,7 +855,7 @@ class _InterpolationSimulation extends Simulation { @override double x(double timeInSeconds) { - final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0); + final double t = clampDouble(timeInSeconds / _durationInSeconds, 0.0, 1.0); if (t == 0.0) return _begin; else if (t == 1.0) diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index c9eb57ce50f8..46ad736a918d 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -182,7 +182,7 @@ class Interval extends Curve { assert(end >= 0.0); assert(end <= 1.0); assert(end >= begin); - t = ((t - begin) / (end - begin)).clamp(0.0, 1.0); + t = clampDouble((t - begin) / (end - begin), 0.0, 1.0); if (t == 0.0 || t == 1.0) return t; return curve.transform(t); diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart index d9e640b22981..f74ab0b46429 100644 --- a/packages/flutter/lib/src/cupertino/context_menu.dart +++ b/packages/flutter/lib/src/cupertino/context_menu.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; import 'dart:ui' as ui; + import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart' show kMinFlingVelocity, kLongPressTimeout; import 'package:flutter/scheduler.dart'; @@ -958,7 +959,7 @@ class _ContextMenuRouteStaticState extends State<_ContextMenuRouteStatic> with T _moveAnimation = Tween( begin: Offset.zero, end: Offset( - endX.clamp(-_kPadding, _kPadding), + clampDouble(endX, -_kPadding, _kPadding), endY, ), ).animate( diff --git a/packages/flutter/lib/src/cupertino/desktop_text_selection.dart b/packages/flutter/lib/src/cupertino/desktop_text_selection.dart index c7e5dcf0ec7a..037c0b059cb4 100644 --- a/packages/flutter/lib/src/cupertino/desktop_text_selection.dart +++ b/packages/flutter/lib/src/cupertino/desktop_text_selection.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -157,7 +158,7 @@ class _CupertinoDesktopTextSelectionControlsToolbarState extends State<_Cupertin final MediaQueryData mediaQuery = MediaQuery.of(context); final Offset midpointAnchor = Offset( - (widget.selectionMidpoint.dx - widget.globalEditableRegion.left).clamp( + clampDouble(widget.selectionMidpoint.dx - widget.globalEditableRegion.left, mediaQuery.padding.left, mediaQuery.size.width - mediaQuery.padding.right, ), diff --git a/packages/flutter/lib/src/cupertino/refresh.dart b/packages/flutter/lib/src/cupertino/refresh.dart index b99debd8faf6..d106165c4cca 100644 --- a/packages/flutter/lib/src/cupertino/refresh.dart +++ b/packages/flutter/lib/src/cupertino/refresh.dart @@ -4,6 +4,7 @@ import 'dart:math'; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; @@ -375,7 +376,7 @@ class CupertinoSliverRefreshControl extends StatefulWidget { double refreshTriggerPullDistance, double refreshIndicatorExtent, ) { - final double percentageComplete = (pulledExtent / refreshTriggerPullDistance).clamp(0.0, 1.0); + final double percentageComplete = clampDouble(pulledExtent / refreshTriggerPullDistance, 0.0, 1.0); // Place the indicator at the top of the sliver that opens up. Note that we're using // a Stack/Positioned widget because the CupertinoActivityIndicator does some internal diff --git a/packages/flutter/lib/src/cupertino/slider.dart b/packages/flutter/lib/src/cupertino/slider.dart index 420cca40febb..b86c282a3e6b 100644 --- a/packages/flutter/lib/src/cupertino/slider.dart +++ b/packages/flutter/lib/src/cupertino/slider.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -434,7 +435,7 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { double _currentDragValue = 0.0; double get _discretizedCurrentDragValue { - double dragValue = _currentDragValue.clamp(0.0, 1.0); + double dragValue = clampDouble(_currentDragValue, 0.0, 1.0); if (divisions != null) dragValue = (dragValue * divisions!).round() / divisions!; return dragValue; @@ -554,8 +555,8 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { config.onIncrease = _increaseAction; config.onDecrease = _decreaseAction; config.value = '${(value * 100).round()}%'; - config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; - config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; + config.increasedValue = '${(clampDouble(value + _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; + config.decreasedValue = '${(clampDouble(value - _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; } } @@ -563,11 +564,11 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { void _increaseAction() { if (isInteractive) - onChanged!((value + _semanticActionUnit).clamp(0.0, 1.0)); + onChanged!(clampDouble(value + _semanticActionUnit, 0.0, 1.0)); } void _decreaseAction() { if (isInteractive) - onChanged!((value - _semanticActionUnit).clamp(0.0, 1.0)); + onChanged!(clampDouble(value - _semanticActionUnit, 0.0, 1.0)); } } diff --git a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart index fd59719f7312..d242e474cdb5 100644 --- a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart +++ b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart @@ -494,7 +494,7 @@ class _SegmentedControlState extends State= 2); - int index = (dx ~/ (renderBox.size.width / numOfChildren)).clamp(0, numOfChildren - 1); + int index = (dx ~/ (renderBox.size.width / numOfChildren)).clamp(0, numOfChildren - 1); // ignore_clamp_double_lint switch (Directionality.of(context)) { case TextDirection.ltr: diff --git a/packages/flutter/lib/src/cupertino/text_selection.dart b/packages/flutter/lib/src/cupertino/text_selection.dart index 3ea2e010069e..662496d6064c 100644 --- a/packages/flutter/lib/src/cupertino/text_selection.dart +++ b/packages/flutter/lib/src/cupertino/text_selection.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -91,7 +92,7 @@ class _CupertinoTextSelectionControlsToolbarState extends State<_CupertinoTextSe // The toolbar should appear below the TextField when there is not enough // space above the TextField to show it, assuming there's always enough // space at the bottom in this case. - final double anchorX = (widget.selectionMidpoint.dx + widget.globalEditableRegion.left).clamp( + final double anchorX = clampDouble(widget.selectionMidpoint.dx + widget.globalEditableRegion.left, _kArrowScreenPadding + mediaQuery.padding.left, mediaQuery.size.width - mediaQuery.padding.right - _kArrowScreenPadding, ); diff --git a/packages/flutter/lib/src/foundation/diagnostics.dart b/packages/flutter/lib/src/foundation/diagnostics.dart index dc77398c84b6..308e2b341976 100644 --- a/packages/flutter/lib/src/foundation/diagnostics.dart +++ b/packages/flutter/lib/src/foundation/diagnostics.dart @@ -9,6 +9,7 @@ import 'package:meta/meta.dart'; import 'assertions.dart'; import 'constants.dart'; import 'debug.dart'; +import 'math.dart' show clampDouble; import 'object.dart'; // Examples can assume: @@ -2044,7 +2045,7 @@ class PercentProperty extends DoubleProperty { final double? v = value; if (v == null) return value.toString(); - return '${(v.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%'; + return '${(clampDouble(v, 0.0, 1.0) * 100.0).toStringAsFixed(1)}%'; } } diff --git a/packages/flutter/lib/src/foundation/math.dart b/packages/flutter/lib/src/foundation/math.dart new file mode 100644 index 000000000000..053192ad506e --- /dev/null +++ b/packages/flutter/lib/src/foundation/math.dart @@ -0,0 +1,23 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Same as [num.clamp] but optimized for non-null [double]. +/// +/// This is faster because it avoids polymorphism, boxing, and special cases for +/// floating point numbers. +// +// See also: //dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart +double clampDouble(double x, double min, double max) { + assert(min <= max && !max.isNaN && !min.isNaN); + if (x < min) { + return min; + } + if (x > max) { + return max; + } + if (x.isNaN) { + return max; + } + return x; +} diff --git a/packages/flutter/lib/src/gestures/force_press.dart b/packages/flutter/lib/src/gestures/force_press.dart index e0c83b099766..a3bea2729b67 100644 --- a/packages/flutter/lib/src/gestures/force_press.dart +++ b/packages/flutter/lib/src/gestures/force_press.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show clampDouble; import 'arena.dart'; import 'events.dart'; import 'recognizer.dart'; @@ -331,7 +332,7 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer { // If the device incorrectly reports a pressure outside of pressureMin // and pressureMax, we still want this recognizer to respond normally. if (!value.isNaN) - value = value.clamp(0.0, 1.0); + value = clampDouble(value, 0.0, 1.0); return value; } diff --git a/packages/flutter/lib/src/material/animated_icons.dart b/packages/flutter/lib/src/material/animated_icons.dart index b38da78160f4..2f3c0ae5e13e 100644 --- a/packages/flutter/lib/src/material/animated_icons.dart +++ b/packages/flutter/lib/src/material/animated_icons.dart @@ -9,6 +9,7 @@ import 'dart:math' as math show pi; import 'dart:ui' as ui show Paint, Path, Canvas; import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; // This package is split into multiple parts to enable a private API that is diff --git a/packages/flutter/lib/src/material/animated_icons/animated_icons.dart b/packages/flutter/lib/src/material/animated_icons/animated_icons.dart index a0afbe8078bd..272cc75c8d0b 100644 --- a/packages/flutter/lib/src/material/animated_icons/animated_icons.dart +++ b/packages/flutter/lib/src/material/animated_icons/animated_icons.dart @@ -161,7 +161,7 @@ class _AnimatedIconPainter extends CustomPainter { } canvas.scale(scale, scale); - final double clampedProgress = progress.value.clamp(0.0, 1.0); + final double clampedProgress = clampDouble(progress.value, 0.0, 1.0); for (final _PathFrames path in paths) path.paint(canvas, color, uiPathFactory, clampedProgress); } diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index af23cfa5efc4..e3114e1206aa 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -1263,7 +1263,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { final bool isScrolledUnder = overlapsContent || (pinned && shrinkOffset > maxExtent - minExtent); final bool isPinnedWithOpacityFade = pinned && floating && bottom != null && extraToolbarHeight == 0.0; final double toolbarOpacity = !pinned || isPinnedWithOpacityFade - ? (visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight)).clamp(0.0, 1.0) + ? clampDouble(visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight), 0.0, 1.0) : 1.0; final Widget appBar = FlexibleSpaceBar.createSettings( @@ -1300,7 +1300,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { titleSpacing: titleSpacing, shape: shape, toolbarOpacity: toolbarOpacity, - bottomOpacity: pinned ? 1.0 : ((visibleMainHeight / _bottomHeight).clamp(0.0, 1.0)), + bottomOpacity: pinned ? 1.0 : clampDouble(visibleMainHeight / _bottomHeight, 0.0, 1.0), toolbarHeight: toolbarHeight, leadingWidth: leadingWidth, backwardsCompatibility: backwardsCompatibility, diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index 8c992282b24a..dcc6ee04be03 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -365,7 +365,7 @@ class _RawMaterialButtonState extends State with MaterialStat right: densityAdjustment.dx, bottom: densityAdjustment.dy, ), - ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); + ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint final Widget result = ConstrainedBox( diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index ab48cf499eb4..ddb6e3f4ae21 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -302,7 +302,7 @@ class _ButtonStyleState extends State with MaterialStateMixin final double dx = math.max(0, densityAdjustment.dx); final EdgeInsetsGeometry padding = resolvedPadding! .add(EdgeInsets.fromLTRB(dx, dy, dx, dy)) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); + .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint // If an opaque button's background is becoming translucent while its // elevation is changing, change the elevation first. Material implicitly diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index cbc65fa4e2d5..65922875af3a 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -1111,7 +1112,7 @@ class _RawChipState extends State with MaterialStateMixin, TickerProvid final EdgeInsetsGeometry defaultLabelPadding = EdgeInsets.lerp( const EdgeInsets.symmetric(horizontal: 8.0), const EdgeInsets.symmetric(horizontal: 4.0), - (MediaQuery.of(context).textScaleFactor - 1.0).clamp(0.0, 1.0), + clampDouble(MediaQuery.of(context).textScaleFactor - 1.0, 0.0, 1.0), )!; final ThemeData theme = Theme.of(context); diff --git a/packages/flutter/lib/src/material/desktop_text_selection.dart b/packages/flutter/lib/src/material/desktop_text_selection.dart index ee3884de376f..0192f0e08f01 100644 --- a/packages/flutter/lib/src/material/desktop_text_selection.dart +++ b/packages/flutter/lib/src/material/desktop_text_selection.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -153,7 +154,7 @@ class _DesktopTextSelectionControlsToolbarState extends State<_DesktopTextSelect final MediaQueryData mediaQuery = MediaQuery.of(context); final Offset midpointAnchor = Offset( - (widget.selectionMidpoint.dx - widget.globalEditableRegion.left).clamp( + clampDouble(widget.selectionMidpoint.dx - widget.globalEditableRegion.left, mediaQuery.padding.left, mediaQuery.size.width - mediaQuery.padding.right, ), diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 3bbe176800d6..f6a0bb547a72 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -4,6 +4,7 @@ import 'dart:ui'; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'color_scheme.dart'; @@ -1176,7 +1177,7 @@ class DialogRoute extends RawDialogRoute { } double _paddingScaleFactor(double textScaleFactor) { - final double clampedTextScaleFactor = textScaleFactor.clamp(1.0, 2.0); + final double clampedTextScaleFactor = clampDouble(textScaleFactor, 1.0, 2.0); // The final padding scale factor is clamped between 1/3 and 1. For example, // a non-scaled padding of 24 will produce a padding between 24 and 8. return lerpDouble(1.0, 1.0 / 3.0, clampedTextScaleFactor - 1.0)!; diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index 70da898cfff3..404472fef319 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -67,12 +67,12 @@ class _DropdownMenuPainter extends CustomPainter { void paint(Canvas canvas, Size size) { final double selectedItemOffset = getSelectedItemOffset(); final Tween top = Tween( - begin: selectedItemOffset.clamp(0.0, math.max(size.height - _kMenuItemHeight, 0.0)), + begin: clampDouble(selectedItemOffset, 0.0, math.max(size.height - _kMenuItemHeight, 0.0)), end: 0.0, ); final Tween bottom = Tween( - begin: (top.begin! + _kMenuItemHeight).clamp(math.min(_kMenuItemHeight, size.height), size.height), + begin: clampDouble(top.begin! + _kMenuItemHeight, math.min(_kMenuItemHeight, size.height), size.height), end: size.height, ); @@ -166,8 +166,8 @@ class _DropdownMenuItemButtonState extends State<_DropdownMenuItemButton> if (widget.itemIndex == widget.route.selectedIndex) { opacity = CurvedAnimation(parent: widget.route.animation!, curve: const Threshold(0.0)); } else { - final double start = (0.5 + (widget.itemIndex + 1) * unit).clamp(0.0, 1.0); - final double end = (start + 1.5 * unit).clamp(0.0, 1.0); + final double start = clampDouble(0.5 + (widget.itemIndex + 1) * unit, 0.0, 1.0); + final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); opacity = CurvedAnimation(parent: widget.route.animation!, curve: Interval(start, end)); } Widget child = Container( @@ -371,10 +371,10 @@ class _DropdownMenuRouteLayout extends SingleChildLayoutDelegate { final double left; switch (textDirection!) { case TextDirection.rtl: - left = buttonRect.right.clamp(0.0, size.width) - childSize.width; + left = clampDouble(buttonRect.right, 0.0, size.width) - childSize.width; break; case TextDirection.ltr: - left = buttonRect.left.clamp(0.0, size.width - childSize.width); + left = clampDouble(buttonRect.left, 0.0, size.width - childSize.width); break; } diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index 58ec0cb05a3a..b4187283f2c5 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'dart:ui' as ui; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'colors.dart'; @@ -231,7 +232,7 @@ class _FlexibleSpaceBarState extends State { // 0.0 -> Expanded // 1.0 -> Collapsed to toolbar - final double t = (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent).clamp(0.0, 1.0); + final double t = clampDouble(1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent, 0.0, 1.0); // background if (widget.background != null) { @@ -307,7 +308,10 @@ class _FlexibleSpaceBarState extends State { if (widget.stretchModes.contains(StretchMode.fadeTitle) && constraints.maxHeight > settings.maxExtent) { final double stretchOpacity = 1 - - (((constraints.maxHeight - settings.maxExtent) / 100).clamp(0.0, 1.0)); + clampDouble( + (constraints.maxHeight - settings.maxExtent) / 100, + 0.0, + 1.0); title = Opacity( opacity: stretchOpacity, child: title, diff --git a/packages/flutter/lib/src/material/input_border.dart b/packages/flutter/lib/src/material/input_border.dart index 3e550fa497d7..185d4ee4e561 100644 --- a/packages/flutter/lib/src/material/input_border.dart +++ b/packages/flutter/lib/src/material/input_border.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; /// Defines the appearance of an [InputDecorator]'s border. @@ -418,7 +419,7 @@ class OutlineInputBorder extends InputBorder { // Currently, BorderRadius only supports circular radii. const double cornerArcSweep = math.pi / 2.0; final double tlCornerArcSweep = math.acos( - (1 - start / scaledRRect.tlRadiusX).clamp(0.0, 1.0), + clampDouble(1 - start / scaledRRect.tlRadiusX, 0.0, 1.0), ); final Path path = Path() @@ -436,7 +437,7 @@ class OutlineInputBorder extends InputBorder { } else if (start + extent < scaledRRect.width) { final double dx = scaledRRect.width - (start + extent); final double sweep = math.asin( - (1 - dx / scaledRRect.trRadiusX).clamp(0.0, 1.0), + clampDouble(1 - dx / scaledRRect.trRadiusX, 0.0, 1.0), ); path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep); } diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index 30e968c3e5dd..f62552440457 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'color_scheme.dart'; @@ -918,7 +919,7 @@ class _ClampTextScaleFactor extends StatelessWidget { Widget build(BuildContext context) { return MediaQuery( data: MediaQuery.of(context).copyWith( - textScaleFactor: MediaQuery.of(context).textScaleFactor.clamp( + textScaleFactor: clampDouble(MediaQuery.of(context).textScaleFactor, 0.0, upperLimit, ), diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index aaefd3da6ad7..30111ba4e1ba 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -543,7 +543,7 @@ class _PopupMenu extends StatelessWidget { for (int i = 0; i < route.items.length; i += 1) { final double start = (i + 1) * unit; - final double end = (start + 1.5 * unit).clamp(0.0, 1.0); + final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); final CurvedAnimation opacity = CurvedAnimation( parent: route.animation!, curve: Interval(start, end), diff --git a/packages/flutter/lib/src/material/progress_indicator.dart b/packages/flutter/lib/src/material/progress_indicator.dart index afc387d49c21..43932ddd45c8 100644 --- a/packages/flutter/lib/src/material/progress_indicator.dart +++ b/packages/flutter/lib/src/material/progress_indicator.dart @@ -202,7 +202,7 @@ class _LinearProgressIndicatorPainter extends CustomPainter { } if (value != null) { - drawBar(0.0, value!.clamp(0.0, 1.0) * size.width); + drawBar(0.0, clampDouble(value!, 0.0, 1.0) * size.width); } else { final double x1 = size.width * line1Tail.transform(animationValue); final double width1 = size.width * line1Head.transform(animationValue) - x1; @@ -385,7 +385,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter { ? _startAngle : _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 2.0 + offsetValue * 0.5 * math.pi, arcSweep = value != null - ? value.clamp(0.0, 1.0) * _sweep + ? clampDouble(value, 0.0, 1.0) * _sweep : math.max(headValue * 3 / 2 * math.pi - tailValue * 3 / 2 * math.pi, _epsilon); final Color? backgroundColor; diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index fdd10e1e79e5..17039d23ddfe 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -1079,7 +1079,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix } double _discretize(double value) { - double result = value.clamp(0.0, 1.0); + double result = clampDouble(value, 0.0, 1.0); if (isDiscrete) { result = (result * divisions!).round() / divisions!; } @@ -1092,7 +1092,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix void _startInteraction(Offset globalPosition) { _state.showValueIndicator(); - final double tapValue = _getValueFromGlobalPosition(globalPosition).clamp(0.0, 1.0); + final double tapValue = clampDouble(_getValueFromGlobalPosition(globalPosition), 0.0, 1.0); _lastThumbSelection = sliderTheme.thumbSelector!(textDirection, values, tapValue, _thumbSize, size, 0); if (_lastThumbSelection != null) { @@ -1613,11 +1613,11 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix } double get _decreasedStartValue { - return (values.start - _semanticActionUnit).clamp(0.0, 1.0); + return clampDouble(values.start - _semanticActionUnit, 0.0, 1.0); } double get _increasedEndValue { - return (values.end + _semanticActionUnit).clamp(0.0, 1.0); + return clampDouble(values.end + _semanticActionUnit, 0.0, 1.0); } double get _decreasedEndValue { diff --git a/packages/flutter/lib/src/material/refresh_indicator.dart b/packages/flutter/lib/src/material/refresh_indicator.dart index e1e88a7ba62a..01fff3623bae 100644 --- a/packages/flutter/lib/src/material/refresh_indicator.dart +++ b/packages/flutter/lib/src/material/refresh_indicator.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'debug.dart'; @@ -409,7 +410,7 @@ class RefreshIndicatorState extends State with TickerProviderS double newValue = _dragOffset! / (containerExtent * _kDragContainerExtentPercentage); if (_mode == _RefreshIndicatorMode.armed) newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit); - _positionController.value = newValue.clamp(0.0, 1.0); // this triggers various rebuilds + _positionController.value = clampDouble(newValue, 0.0, 1.0); // this triggers various rebuilds if (_mode == _RefreshIndicatorMode.drag && _valueColor.value!.alpha == 0xFF) _mode = _RefreshIndicatorMode.armed; } diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 00807ce9f16e..b9f6538639c8 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -993,7 +993,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { if (extendBody) { bodyMaxHeight += bottomWidgetsHeight; - bodyMaxHeight = bodyMaxHeight.clamp(0.0, looseConstraints.maxHeight - contentTop); + bodyMaxHeight = clampDouble(bodyMaxHeight, 0.0, looseConstraints.maxHeight - contentTop); assert(bodyMaxHeight <= math.max(0.0, looseConstraints.maxHeight - contentTop)); } @@ -2366,7 +2366,7 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto /// [Scaffold.floatingActionButton]. This value must not be null. set _floatingActionButtonVisibilityValue(double newValue) { assert(newValue != null); - _floatingActionButtonVisibilityController.value = newValue.clamp( + _floatingActionButtonVisibilityController.value = clampDouble(newValue, _floatingActionButtonVisibilityController.lowerBound, _floatingActionButtonVisibilityController.upperBound, ); diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index f0d16818e3a0..78ac129bac75 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -1273,7 +1273,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } double _discretize(double value) { - double result = value.clamp(0.0, 1.0); + double result = clampDouble(value, 0.0, 1.0); if (isDiscrete) { result = (result * divisions!).round() / divisions!; } @@ -1540,12 +1540,12 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { if (semanticFormatterCallback != null) { config.value = semanticFormatterCallback!(_state._lerp(value)); - config.increasedValue = semanticFormatterCallback!(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0))); - config.decreasedValue = semanticFormatterCallback!(_state._lerp((value - _semanticActionUnit).clamp(0.0, 1.0))); + config.increasedValue = semanticFormatterCallback!(_state._lerp(clampDouble(value + _semanticActionUnit, 0.0, 1.0))); + config.decreasedValue = semanticFormatterCallback!(_state._lerp(clampDouble(value - _semanticActionUnit, 0.0, 1.0))); } else { config.value = '${(value * 100).round()}%'; - config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; - config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; + config.increasedValue = '${(clampDouble(value + _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; + config.decreasedValue = '${(clampDouble(value - _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; } } @@ -1553,13 +1553,13 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { void increaseAction() { if (isInteractive) { - onChanged!((value + _semanticActionUnit).clamp(0.0, 1.0)); + onChanged!(clampDouble(value + _semanticActionUnit, 0.0, 1.0)); } } void decreaseAction() { if (isInteractive) { - onChanged!((value - _semanticActionUnit).clamp(0.0, 1.0)); + onChanged!(clampDouble(value - _semanticActionUnit, 0.0, 1.0)); } } } diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart index 1bfb0979fdcf..0b919c36b269 100644 --- a/packages/flutter/lib/src/material/slider_theme.dart +++ b/packages/flutter/lib/src/material/slider_theme.dart @@ -3180,7 +3180,7 @@ class _PaddleSliderValueIndicatorPathPainter { // factor of the value indicator. final double neckStretchBaseline = math.max(0.0, rightBottomNeckCenterY - math.max(leftTopNeckCenter.dy, neckRightCenter.dy)); final double t = math.pow(inverseTextScale, 3.0) as double; - final double stretch = (neckStretchBaseline * t).clamp(0.0, 10.0 * neckStretchBaseline); + final double stretch = clampDouble(neckStretchBaseline * t, 0.0, 10.0 * neckStretchBaseline); final Offset neckStretch = Offset(0.0, neckStretchBaseline - stretch); assert(!_debuggingLabelLocation || () { diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 58f78e8ddf63..49ddfea34948 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -311,7 +311,7 @@ double _indexChangeProgress(TabController controller) { // The controller's offset is changing because the user is dragging the // TabBarView's PageView to the left or right. if (!controller.indexIsChanging) - return (currentIndex - controllerValue).abs().clamp(0.0, 1.0); + return clampDouble((currentIndex - controllerValue).abs(), 0.0, 1.0); // The TabController animation's value is changing from previousIndex to currentIndex. return (controllerValue - currentIndex).abs() / (currentIndex - previousIndex).abs(); @@ -417,8 +417,8 @@ class _IndicatorPainter extends CustomPainter { final double index = controller.index.toDouble(); final double value = controller.animation!.value; final bool ltr = index > value; - final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex); - final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex); + final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex); // ignore_clamp_double_lint + final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex); // ignore_clamp_double_lint final Rect fromRect = indicatorRect(size, from); final Rect toRect = indicatorRect(size, to); _currentRect = Rect.lerp(fromRect, toRect, (value - from).abs()); @@ -491,8 +491,8 @@ class _DragAnimation extends Animation with AnimationWithParentMixin { case TextDirection.ltr: break; } - return (tabCenter - viewportWidth / 2.0).clamp(minExtent, maxExtent); + return clampDouble(tabCenter - viewportWidth / 2.0, minExtent, maxExtent); } double _tabCenteredScrollOffset(int index) { @@ -1510,12 +1510,12 @@ class _TabBarViewState extends State { _controller!.index = _pageController.page!.round(); _currentIndex =_controller!.index; } - _controller!.offset = (_pageController.page! - _controller!.index).clamp(-1.0, 1.0); + _controller!.offset = clampDouble(_pageController.page! - _controller!.index, -1.0, 1.0); } else if (notification is ScrollEndNotification) { _controller!.index = _pageController.page!.round(); _currentIndex = _controller!.index; if (!_controller!.indexIsChanging) - _controller!.offset = (_pageController.page! - _controller!.index).clamp(-1.0, 1.0); + _controller!.offset = clampDouble(_pageController.page! - _controller!.index, -1.0, 1.0); } _warpUnderwayCount -= 1; diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 0bf3addeb3d4..679109f821df 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -898,7 +898,7 @@ class _TextFieldState extends State with RestorationMixin implements if (widget.maxLength! > 0) { // Show the maxLength in the counter counterText += '/${widget.maxLength}'; - final int remaining = (widget.maxLength! - currentLength).clamp(0, widget.maxLength!); + final int remaining = (widget.maxLength! - currentLength).clamp(0, widget.maxLength!); // ignore_clamp_double_lint semanticCounterText = localizations.remainingTextFieldCharacterCount(remaining); } diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 7ef7b2b89a59..26ac146d7407 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -2686,8 +2686,8 @@ class VisualDensity with Diagnosticable { BoxConstraints effectiveConstraints(BoxConstraints constraints) { assert(constraints != null && constraints.debugAssertIsValid()); return constraints.copyWith( - minWidth: (constraints.minWidth + baseSizeAdjustment.dx).clamp(0.0, constraints.maxWidth), - minHeight: (constraints.minHeight + baseSizeAdjustment.dy).clamp(0.0, constraints.maxHeight), + minWidth: clampDouble(constraints.minWidth + baseSizeAdjustment.dx, 0.0, constraints.maxWidth), + minHeight: clampDouble(constraints.minHeight + baseSizeAdjustment.dy, 0.0, constraints.maxHeight), ); } diff --git a/packages/flutter/lib/src/painting/colors.dart b/packages/flutter/lib/src/painting/colors.dart index 4acde694c947..60c81abfd3c1 100644 --- a/packages/flutter/lib/src/painting/colors.dart +++ b/packages/flutter/lib/src/painting/colors.dart @@ -206,10 +206,10 @@ class HSVColor { if (b == null) return a._scaleAlpha(1.0 - t); return HSVColor.fromAHSV( - lerpDouble(a.alpha, b.alpha, t)!.clamp(0.0, 1.0), + clampDouble(lerpDouble(a.alpha, b.alpha, t)!, 0.0, 1.0), lerpDouble(a.hue, b.hue, t)! % 360.0, - lerpDouble(a.saturation, b.saturation, t)!.clamp(0.0, 1.0), - lerpDouble(a.value, b.value, t)!.clamp(0.0, 1.0), + clampDouble(lerpDouble(a.saturation, b.saturation, t)!, 0.0, 1.0), + clampDouble(lerpDouble(a.value, b.value, t)!, 0.0, 1.0), ); } @@ -290,7 +290,7 @@ class HSLColor { // Saturation can exceed 1.0 with rounding errors, so clamp it. final double saturation = lightness == 1.0 ? 0.0 - : ((delta / (1.0 - (2.0 * lightness - 1.0).abs())).clamp(0.0, 1.0)); + : clampDouble(delta / (1.0 - (2.0 * lightness - 1.0).abs()), 0.0, 1.0); return HSLColor.fromAHSL(alpha, hue, saturation, lightness); } @@ -390,10 +390,10 @@ class HSLColor { if (b == null) return a._scaleAlpha(1.0 - t); return HSLColor.fromAHSL( - lerpDouble(a.alpha, b.alpha, t)!.clamp(0.0, 1.0), + clampDouble(lerpDouble(a.alpha, b.alpha, t)!, 0.0, 1.0), lerpDouble(a.hue, b.hue, t)! % 360.0, - lerpDouble(a.saturation, b.saturation, t)!.clamp(0.0, 1.0), - lerpDouble(a.lightness, b.lightness, t)!.clamp(0.0, 1.0), + clampDouble(lerpDouble(a.saturation, b.saturation, t)!, 0.0, 1.0), + clampDouble(lerpDouble(a.lightness, b.lightness, t)!, 0.0, 1.0), ); } diff --git a/packages/flutter/lib/src/painting/edge_insets.dart b/packages/flutter/lib/src/painting/edge_insets.dart index 4c11af16eaed..6ba1eb6f8ca8 100644 --- a/packages/flutter/lib/src/painting/edge_insets.dart +++ b/packages/flutter/lib/src/painting/edge_insets.dart @@ -161,12 +161,12 @@ abstract class EdgeInsetsGeometry { /// or equal to `min`, and less than or equal to `max`. EdgeInsetsGeometry clamp(EdgeInsetsGeometry min, EdgeInsetsGeometry max) { return _MixedEdgeInsets.fromLRSETB( - _left.clamp(min._left, max._left), - _right.clamp(min._right, max._right), - _start.clamp(min._start, max._start), - _end.clamp(min._end, max._end), - _top.clamp(min._top, max._top), - _bottom.clamp(min._bottom, max._bottom), + clampDouble(_left, min._left, max._left), + clampDouble(_right, min._right, max._right), + clampDouble(_start, min._start, max._start), + clampDouble(_end, min._end, max._end), + clampDouble(_top, min._top, max._top), + clampDouble(_bottom, min._bottom, max._bottom), ); } @@ -505,10 +505,10 @@ class EdgeInsets extends EdgeInsetsGeometry { @override EdgeInsetsGeometry clamp(EdgeInsetsGeometry min, EdgeInsetsGeometry max) { return EdgeInsets.fromLTRB( - _left.clamp(min._left, max._left), - _top.clamp(min._top, max._top), - _right.clamp(min._right, max._right), - _bottom.clamp(min._bottom, max._bottom), + clampDouble(_left, min._left, max._left), + clampDouble(_top, min._top, max._top), + clampDouble(_right, min._right, max._right), + clampDouble(_bottom, min._bottom, max._bottom), ); } diff --git a/packages/flutter/lib/src/painting/flutter_logo.dart b/packages/flutter/lib/src/painting/flutter_logo.dart index 66895ce7dd90..a4fe8a1932e3 100644 --- a/packages/flutter/lib/src/painting/flutter_logo.dart +++ b/packages/flutter/lib/src/painting/flutter_logo.dart @@ -119,7 +119,7 @@ class FlutterLogoDecoration extends Decoration { b.style, b.margin * t, b._position, - b._opacity * t.clamp(0.0, 1.0), + b._opacity * clampDouble(t, 0.0, 1.0), ); } if (b == null) { @@ -128,7 +128,7 @@ class FlutterLogoDecoration extends Decoration { a.style, a.margin * t, a._position, - a._opacity * (1.0 - t).clamp(0.0, 1.0), + a._opacity * clampDouble(1.0 - t, 0.0, 1.0), ); } if (t == 0.0) @@ -140,7 +140,7 @@ class FlutterLogoDecoration extends Decoration { t < 0.5 ? a.style : b.style, EdgeInsets.lerp(a.margin, b.margin, t)!, a._position + (b._position - a._position) * t, - (a._opacity + (b._opacity - a._opacity) * t).clamp(0.0, 1.0), + clampDouble(a._opacity + (b._opacity - a._opacity) * t, 0.0, 1.0), ); } diff --git a/packages/flutter/lib/src/painting/geometry.dart b/packages/flutter/lib/src/painting/geometry.dart index 94aa59b8173d..3e61c68bf50e 100644 --- a/packages/flutter/lib/src/painting/geometry.dart +++ b/packages/flutter/lib/src/painting/geometry.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'basic_types.dart'; /// Position a child box within a container box, either above or below a target @@ -64,7 +65,7 @@ Offset positionDependentBox({ if (size.width - margin * 2.0 < childSize.width) { x = (size.width - childSize.width) / 2.0; } else { - final double normalizedTargetX = target.dx.clamp(margin, size.width - margin); + final double normalizedTargetX = clampDouble(target.dx, margin, size.width - margin); final double edge = margin + childSize.width / 2.0; if (normalizedTargetX < edge) { x = margin; diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 690a96c19c62..2b37f23b7b75 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -621,7 +621,7 @@ class TextPainter { newWidth = maxIntrinsicWidth; break; } - newWidth = newWidth.clamp(minWidth, maxWidth); + newWidth = clampDouble(newWidth, minWidth, maxWidth); if (newWidth != _applyFloatingPointHack(_paragraph!.width)) { _paragraph!.layout(ui.ParagraphConstraints(width: newWidth)); } @@ -781,7 +781,8 @@ class TextPainter { final double caretEnd = box.end; final double dx = box.direction == TextDirection.rtl ? caretEnd - caretPrototype.width : caretEnd; - return Rect.fromLTRB(dx.clamp(0, _paragraph!.width), box.top, dx.clamp(0, _paragraph!.width), box.bottom); + return Rect.fromLTRB(clampDouble(dx, 0, _paragraph!.width), box.top, + clampDouble(dx, 0, _paragraph!.width), box.bottom); } return null; } @@ -823,7 +824,7 @@ class TextPainter { final TextBox box = boxes.last; final double caretStart = box.start; final double dx = box.direction == TextDirection.rtl ? caretStart - caretPrototype.width : caretStart; - return Rect.fromLTRB(dx.clamp(0, _paragraph!.width), box.top, dx.clamp(0, _paragraph!.width), box.bottom); + return Rect.fromLTRB(clampDouble(dx, 0, _paragraph!.width), box.top, clampDouble(dx, 0, _paragraph!.width), box.bottom); } return null; } diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index 2bfbee3f3669..0d948cd7dae6 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -967,7 +967,7 @@ class TextStyle with Diagnosticable { fontFamily: fontFamily ?? _fontFamily, fontFamilyFallback: fontFamilyFallback ?? this.fontFamilyFallback, fontSize: fontSize == null ? null : fontSize! * fontSizeFactor + fontSizeDelta, - fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)], + fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)], // ignore_clamp_double_lint fontStyle: fontStyle ?? this.fontStyle, letterSpacing: letterSpacing == null ? null : letterSpacing! * letterSpacingFactor + letterSpacingDelta, wordSpacing: wordSpacing == null ? null : wordSpacing! * wordSpacingFactor + wordSpacingDelta, diff --git a/packages/flutter/lib/src/physics/clamped_simulation.dart b/packages/flutter/lib/src/physics/clamped_simulation.dart index 6c5b7bb05571..0454e914318b 100644 --- a/packages/flutter/lib/src/physics/clamped_simulation.dart +++ b/packages/flutter/lib/src/physics/clamped_simulation.dart @@ -55,10 +55,10 @@ class ClampedSimulation extends Simulation { final double dxMax; @override - double x(double time) => simulation.x(time).clamp(xMin, xMax); + double x(double time) => clampDouble(simulation.x(time), xMin, xMax); @override - double dx(double time) => simulation.dx(time).clamp(dxMin, dxMax); + double dx(double time) => clampDouble(simulation.dx(time), dxMin, dxMax); @override bool isDone(double time) => simulation.isDone(time); diff --git a/packages/flutter/lib/src/physics/friction_simulation.dart b/packages/flutter/lib/src/physics/friction_simulation.dart index 868243bd9c9c..9c24c07a6056 100644 --- a/packages/flutter/lib/src/physics/friction_simulation.dart +++ b/packages/flutter/lib/src/physics/friction_simulation.dart @@ -115,14 +115,14 @@ class BoundedFrictionSimulation extends FrictionSimulation { super.velocity, this._minX, this._maxX, - ) : assert(position.clamp(_minX, _maxX) == position); + ) : assert(clampDouble(position, _minX, _maxX) == position); final double _minX; final double _maxX; @override double x(double time) { - return super.x(time).clamp(_minX, _maxX); + return clampDouble(super.x(time), _minX, _maxX); } @override diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index 0d508c736f8d..563414b5c941 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -212,10 +212,10 @@ class BoxConstraints extends Constraints { /// as close as possible to the original constraints. BoxConstraints enforce(BoxConstraints constraints) { return BoxConstraints( - minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth), - maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth), - minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight), - maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight), + minWidth: clampDouble(minWidth, constraints.minWidth, constraints.maxWidth), + maxWidth: clampDouble(maxWidth, constraints.minWidth, constraints.maxWidth), + minHeight: clampDouble(minHeight, constraints.minHeight, constraints.maxHeight), + maxHeight: clampDouble(maxHeight, constraints.minHeight, constraints.maxHeight), ); } @@ -224,10 +224,10 @@ class BoxConstraints extends Constraints { /// box constraints. BoxConstraints tighten({ double? width, double? height }) { return BoxConstraints( - minWidth: width == null ? minWidth : width.clamp(minWidth, maxWidth), - maxWidth: width == null ? maxWidth : width.clamp(minWidth, maxWidth), - minHeight: height == null ? minHeight : height.clamp(minHeight, maxHeight), - maxHeight: height == null ? maxHeight : height.clamp(minHeight, maxHeight), + minWidth: width == null ? minWidth : clampDouble(width, minWidth, maxWidth), + maxWidth: width == null ? maxWidth : clampDouble(width, minWidth, maxWidth), + minHeight: height == null ? minHeight : clampDouble(height, minHeight, maxHeight), + maxHeight: height == null ? maxHeight : clampDouble(height, minHeight, maxHeight), ); } @@ -253,14 +253,14 @@ class BoxConstraints extends Constraints { /// possible to the given width. double constrainWidth([ double width = double.infinity ]) { assert(debugAssertIsValid()); - return width.clamp(minWidth, maxWidth); + return clampDouble(width, minWidth, maxWidth); } /// Returns the height that both satisfies the constraints and is as close as /// possible to the given height. double constrainHeight([ double height = double.infinity ]) { assert(debugAssertIsValid()); - return height.clamp(minHeight, maxHeight); + return clampDouble(height, minHeight, maxHeight); } Size _debugPropagateDebugSize(Size size, Size result) { diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index f896509332fe..350088384236 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1651,8 +1651,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, final Offset start = Offset(0.0, preferredLineHeight) + caretOffset + paintOffset; return [TextSelectionPoint(start, null)]; } else { - final Offset start = Offset(boxes.first.start.clamp(0, _textPainter.size.width), boxes.first.bottom) + paintOffset; - final Offset end = Offset(boxes.last.end.clamp(0, _textPainter.size.width), boxes.last.bottom) + paintOffset; + final Offset start = Offset(clampDouble(boxes.first.start, 0, _textPainter.size.width), boxes.first.bottom) + paintOffset; + final Offset end = Offset(clampDouble(boxes.last.end, 0, _textPainter.size.width), boxes.last.bottom) + paintOffset; return [ TextSelectionPoint(start, boxes.first.direction), TextSelectionPoint(end, boxes.last.direction), @@ -2480,8 +2480,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, void _paintHandleLayers(PaintingContext context, List endpoints) { Offset startPoint = endpoints[0].point; startPoint = Offset( - startPoint.dx.clamp(0.0, size.width), - startPoint.dy.clamp(0.0, size.height), + clampDouble(startPoint.dx, 0.0, size.width), + clampDouble(startPoint.dy, 0.0, size.height), ); context.pushLayer( LeaderLayer(link: startHandleLayerLink, offset: startPoint), @@ -2491,8 +2491,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, if (endpoints.length == 2) { Offset endPoint = endpoints[1].point; endPoint = Offset( - endPoint.dx.clamp(0.0, size.width), - endPoint.dy.clamp(0.0, size.height), + clampDouble(endPoint.dx, 0.0, size.width), + clampDouble(endPoint.dy, 0.0, size.height), ); context.pushLayer( LeaderLayer(link: endHandleLayerLink, offset: endPoint), diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart index e759d4fe8243..47c2c0444b8c 100644 --- a/packages/flutter/lib/src/rendering/sliver.dart +++ b/packages/flutter/lib/src/rendering/sliver.dart @@ -1337,7 +1337,7 @@ abstract class RenderSliver extends RenderObject { final double a = constraints.scrollOffset; final double b = constraints.scrollOffset + constraints.remainingPaintExtent; // the clamp on the next line is to avoid floating point rounding errors - return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingPaintExtent); + return clampDouble(clampDouble(to, a, b) - clampDouble(from, a, b), 0.0, constraints.remainingPaintExtent); } /// Computes the portion of the region from `from` to `to` that is within @@ -1353,7 +1353,7 @@ abstract class RenderSliver extends RenderObject { final double a = constraints.scrollOffset + constraints.cacheOrigin; final double b = constraints.scrollOffset + constraints.remainingCacheExtent; // the clamp on the next line is to avoid floating point rounding errors - return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingCacheExtent); + return clampDouble(clampDouble(to, a, b) - clampDouble(from, a, b), 0.0, constraints.remainingCacheExtent); } /// Returns the distance from the leading _visible_ edge of the sliver to the diff --git a/packages/flutter/lib/src/rendering/sliver_grid.dart b/packages/flutter/lib/src/rendering/sliver_grid.dart index 17cafcea2d95..fc7650a86c7a 100644 --- a/packages/flutter/lib/src/rendering/sliver_grid.dart +++ b/packages/flutter/lib/src/rendering/sliver_grid.dart @@ -570,10 +570,10 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { if (firstChild != null) { final int oldFirstIndex = indexOf(firstChild!); final int oldLastIndex = indexOf(lastChild!); - final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount); + final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount); // ignore_clamp_double_lint final int trailingGarbage = targetLastIndex == null ? 0 - : (oldLastIndex - targetLastIndex).clamp(0, childCount); + : (oldLastIndex - targetLastIndex).clamp(0, childCount); // ignore_clamp_double_lint collectGarbage(leadingGarbage, trailingGarbage); } else { collectGarbage(0, 0); diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart index 7904484a5c9c..c2706a9c1e87 100644 --- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart @@ -364,7 +364,7 @@ abstract class RenderSliverScrollingPersistentHeader extends RenderSliverPersist geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), - paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent), + paintExtent: clampDouble(paintExtent, 0.0, constraints.remainingPaintExtent), maxPaintExtent: maxExtent + stretchOffset, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. ); @@ -381,7 +381,7 @@ abstract class RenderSliverScrollingPersistentHeader extends RenderSliverPersist geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), - paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent), + paintExtent: clampDouble(paintExtent, 0.0, constraints.remainingPaintExtent), maxPaintExtent: maxExtent, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. ); @@ -423,7 +423,7 @@ abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistent final bool overlapsContent = constraints.overlap > 0.0; layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent); final double effectiveRemainingPaintExtent = math.max(0, constraints.remainingPaintExtent - constraints.overlap); - final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, effectiveRemainingPaintExtent); + final double layoutExtent = clampDouble(maxExtent - constraints.scrollOffset, 0.0, effectiveRemainingPaintExtent); final double stretchOffset = stretchConfiguration != null ? constraints.overlap.abs() : 0.0; @@ -595,8 +595,8 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), - paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent), - layoutExtent: layoutExtent.clamp(0.0, constraints.remainingPaintExtent), + paintExtent: clampDouble(paintExtent, 0.0, constraints.remainingPaintExtent), + layoutExtent: clampDouble(layoutExtent, 0.0, constraints.remainingPaintExtent), maxPaintExtent: maxExtent + stretchOffset, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. ); @@ -677,7 +677,7 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste if (delta > 0.0) // If we are trying to expand when allowFloatingExpansion is false, delta = 0.0; // disallow the expansion. (But allow shrinking, i.e. delta < 0.0 is fine.) } - _effectiveScrollOffset = (_effectiveScrollOffset! - delta).clamp(0.0, constraints.scrollOffset); + _effectiveScrollOffset = clampDouble(_effectiveScrollOffset! - delta, 0.0, constraints.scrollOffset); } else { _effectiveScrollOffset = constraints.scrollOffset; } @@ -738,13 +738,16 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste // A stretch header can have a bigger childExtent than maxExtent. final double effectiveMaxExtent = math.max(childExtent, maxExtent); - targetExtent = targetExtent.clamp( - showOnScreen.minShowOnScreenExtent, - showOnScreen.maxShowOnScreenExtent, - ) - // Clamp the value back to the valid range after applying additional - // constraints. Contracting is not allowed. - .clamp(childExtent, effectiveMaxExtent); + targetExtent = clampDouble( + clampDouble( + targetExtent, + showOnScreen.minShowOnScreenExtent, + showOnScreen.maxShowOnScreenExtent, + ), + // Clamp the value back to the valid range after applying additional + // constraints. Contracting is not allowed. + childExtent, + effectiveMaxExtent); // Expands the header if needed, with animation. if (targetExtent > childExtent) { @@ -806,7 +809,7 @@ abstract class RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFl constraints.remainingPaintExtent; final double maxExtent = this.maxExtent; final double paintExtent = maxExtent - _effectiveScrollOffset!; - final double clampedPaintExtent = paintExtent.clamp( + final double clampedPaintExtent = clampDouble(paintExtent, minAllowedExtent, constraints.remainingPaintExtent, ); @@ -818,7 +821,7 @@ abstract class RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFl scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), paintExtent: clampedPaintExtent, - layoutExtent: layoutExtent.clamp(0.0, clampedPaintExtent), + layoutExtent: clampDouble(layoutExtent, 0.0, clampedPaintExtent), maxPaintExtent: maxExtent + stretchOffset, maxScrollObstructionExtent: minExtent, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index fbc98bc9df73..1c00d18cf79f 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -1539,8 +1539,8 @@ class RenderViewport extends RenderViewportBase(hasDragged - ? _currentSize.value.clamp(minSize, maxSize) + ? clampDouble(_currentSize.value, minSize, maxSize) : initialSize), hasDragged: hasDragged, ); diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 5eb767eb5388..ec755a7e7db9 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2264,7 +2264,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien ? editableSize.width / 2 - rect.center.dx // Valid additional offsets range from (rect.right - size.width) // to (rect.left). Pick the closest one if out of range. - : 0.0.clamp(rect.right - editableSize.width, rect.left); + : clampDouble(0.0, rect.right - editableSize.width, rect.left); unitOffset = const Offset(1, 0); } else { // The caret is vertically centered within the line. Expand the caret's @@ -2278,17 +2278,17 @@ class EditableTextState extends State with AutomaticKeepAliveClien additionalOffset = expandedRect.height >= editableSize.height ? editableSize.height / 2 - expandedRect.center.dy - : 0.0.clamp(expandedRect.bottom - editableSize.height, expandedRect.top); + : clampDouble(0.0, expandedRect.bottom - editableSize.height, expandedRect.top); unitOffset = const Offset(0, 1); } // No overscrolling when encountering tall fonts/scripts that extend past // the ascent. - final double targetOffset = (additionalOffset + _scrollController.offset) - .clamp( - _scrollController.position.minScrollExtent, - _scrollController.position.maxScrollExtent, - ); + final double targetOffset = clampDouble( + additionalOffset + _scrollController.offset, + _scrollController.position.minScrollExtent, + _scrollController.position.maxScrollExtent, + ); final double offsetDelta = _scrollController.offset - targetOffset; return RevealedOffset(rect: rect.shift(unitOffset * offsetDelta), offset: targetOffset); diff --git a/packages/flutter/lib/src/widgets/icon_theme_data.dart b/packages/flutter/lib/src/widgets/icon_theme_data.dart index c1af339a4e1b..0381efb16ea3 100644 --- a/packages/flutter/lib/src/widgets/icon_theme_data.dart +++ b/packages/flutter/lib/src/widgets/icon_theme_data.dart @@ -84,7 +84,7 @@ class IconThemeData with Diagnosticable { final Color? color; /// An opacity to apply to both explicit and default icon colors. - double? get opacity => _opacity?.clamp(0.0, 1.0); + double? get opacity => _opacity == null ? null : clampDouble(_opacity!, 0.0, 1.0); final double? _opacity; /// The default size for icons. diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index 56665362cada..78cc6274235d 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -848,7 +848,7 @@ class _AnimatedPaddingState extends AnimatedWidgetBaseState { return Padding( padding: _padding! .evaluate(animation) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity), + .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity), // ignore_clamp_double_lint child: widget.child, ); } diff --git a/packages/flutter/lib/src/widgets/interactive_viewer.dart b/packages/flutter/lib/src/widgets/interactive_viewer.dart index 3362e067c4f8..e9cc1f0ca587 100644 --- a/packages/flutter/lib/src/widgets/interactive_viewer.dart +++ b/packages/flutter/lib/src/widgets/interactive_viewer.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart'; import 'package:flutter/physics.dart'; import 'package:vector_math/vector_math_64.dart' show Quad, Vector3, Matrix4; @@ -386,7 +387,7 @@ class InteractiveViewer extends StatefulWidget { // the point. final Vector3 l1P = point - l1; final Vector3 l1L2 = l2 - l1; - final double fraction = (l1P.dot(l1L2) / lengthSquared).clamp(0.0, 1.0); + final double fraction = clampDouble(l1P.dot(l1L2) / lengthSquared, 0.0, 1.0); return l1 + l1L2 * fraction; } @@ -659,7 +660,7 @@ class _InteractiveViewerState extends State with TickerProvid _viewport.height / _boundaryRect.height, ), ); - final double clampedTotalScale = totalScale.clamp( + final double clampedTotalScale = clampDouble(totalScale, widget.minScale, widget.maxScale, ); diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 278164337108..ce689c53a344 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -732,7 +732,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont double pixels, minRange, maxRange, correctionOffset; double extra = 0.0; if (innerPosition.pixels == innerPosition.minScrollExtent) { - pixels = _outerPosition!.pixels.clamp( + pixels = clampDouble(_outerPosition!.pixels, _outerPosition!.minScrollExtent, _outerPosition!.maxScrollExtent, ); // TODO(ianh): gracefully handle out-of-range outer positions @@ -799,7 +799,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont double unnestOffset(double value, _NestedScrollPosition source) { if (source == _outerPosition) - return value.clamp( + return clampDouble(value, _outerPosition!.minScrollExtent, _outerPosition!.maxScrollExtent, ); @@ -810,7 +810,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont double nestOffset(double value, _NestedScrollPosition target) { if (target == _outerPosition) - return value.clamp( + return clampDouble(value, _outerPosition!.minScrollExtent, _outerPosition!.maxScrollExtent, ); @@ -1212,7 +1212,7 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele // 0.0, representing the end of the overscrolled portion. : pixels < 0.0 ? 0.0 : math.max(maxScrollExtent, pixels); final double oldPixels = pixels; - final double newPixels = (pixels - delta).clamp(min, max); + final double newPixels = clampDouble(pixels - delta, min, max); final double clampedDelta = newPixels - pixels; if (clampedDelta == 0.0) return delta; @@ -1268,7 +1268,7 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele final double max = delta < 0.0 ? double.infinity : math.max(maxScrollExtent, pixels); - final double newPixels = (pixels + delta).clamp(min, max); + final double newPixels = clampDouble(pixels + delta, min, max); final double clampedDelta = newPixels - pixels; if (clampedDelta == 0.0) return delta; @@ -1494,7 +1494,7 @@ class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity { done = true; } } else { - value = value.clamp(metrics.minRange, metrics.maxRange); + value = clampDouble(value, metrics.minRange, metrics.maxRange); done = true; } final bool result = super.applyMoveTo(value + metrics.correctionOffset); diff --git a/packages/flutter/lib/src/widgets/overscroll_indicator.dart b/packages/flutter/lib/src/widgets/overscroll_indicator.dart index 329f21235479..143930479d39 100644 --- a/packages/flutter/lib/src/widgets/overscroll_indicator.dart +++ b/packages/flutter/lib/src/widgets/overscroll_indicator.dart @@ -255,10 +255,10 @@ class _GlowingOverscrollIndicatorState extends State final Offset position = renderer.globalToLocal(notification.dragDetails!.globalPosition); switch (notification.metrics.axis) { case Axis.horizontal: - controller!.pull(notification.overscroll.abs(), size.width, position.dy.clamp(0.0, size.height), size.height); + controller!.pull(notification.overscroll.abs(), size.width, clampDouble(position.dy, 0.0, size.height), size.height); break; case Axis.vertical: - controller!.pull(notification.overscroll.abs(), size.height, position.dx.clamp(0.0, size.width), size.width); + controller!.pull(notification.overscroll.abs(), size.height, clampDouble(position.dx, 0.0, size.width), size.width); break; } } @@ -405,9 +405,9 @@ class _GlowController extends ChangeNotifier { assert(velocity >= 0.0); _pullRecedeTimer?.cancel(); _pullRecedeTimer = null; - velocity = velocity.clamp(_minVelocity, _maxVelocity); + velocity = clampDouble(velocity, _minVelocity, _maxVelocity); _glowOpacityTween.begin = _state == _GlowState.idle ? 0.3 : _glowOpacity.value; - _glowOpacityTween.end = (velocity * _velocityGlowFactor).clamp(_glowOpacityTween.begin!, _maxOpacity); + _glowOpacityTween.end = clampDouble(velocity * _velocityGlowFactor, _glowOpacityTween.begin!, _maxOpacity); _glowSizeTween.begin = _glowSize.value; _glowSizeTween.end = math.min(0.025 + 7.5e-7 * velocity * velocity, 1.0); _glowController.duration = Duration(milliseconds: (0.15 + velocity * 0.02).round()); @@ -716,7 +716,7 @@ class _StretchingOverscrollIndicatorState extends State= 0.0); - velocity = velocity.clamp(1, 10000); + velocity = clampDouble(velocity, 1, 10000); _stretchSizeTween.begin = _stretchSize.value; _stretchSizeTween.end = math.min(_stretchIntensity + (_flingFriction / velocity), 1.0); _stretchController.duration = Duration(milliseconds: (velocity * 0.02).round()); diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index ee27b4038a45..04beaf5a9409 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -4,7 +4,7 @@ import 'dart:math' as math; -import 'package:flutter/foundation.dart' show precisionErrorTolerance; +import 'package:flutter/foundation.dart' show precisionErrorTolerance, clampDouble; import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/rendering.dart'; @@ -296,7 +296,7 @@ class PageMetrics extends FixedScrollMetrics { /// The current page displayed in the [PageView]. double? get page { - return math.max(0.0, pixels.clamp(minScrollExtent, maxScrollExtent)) / + return math.max(0.0, clampDouble(pixels, minScrollExtent, maxScrollExtent)) / math.max(1.0, viewportDimension * viewportFraction); } @@ -396,7 +396,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri ); return !hasPixels || !hasContentDimensions ? null - : _cachedPage ?? getPageFromPixels(pixels.clamp(minScrollExtent, maxScrollExtent), viewportDimension); + : _cachedPage ?? getPageFromPixels(clampDouble(pixels, minScrollExtent, maxScrollExtent), viewportDimension); } @override diff --git a/packages/flutter/lib/src/widgets/scroll_metrics.dart b/packages/flutter/lib/src/widgets/scroll_metrics.dart index 3231ac6c3230..10c20da60c46 100644 --- a/packages/flutter/lib/src/widgets/scroll_metrics.dart +++ b/packages/flutter/lib/src/widgets/scroll_metrics.dart @@ -116,9 +116,9 @@ mixin ScrollMetrics { assert(minScrollExtent <= maxScrollExtent); return viewportDimension // "above" overscroll value - - (minScrollExtent - pixels).clamp(0, viewportDimension) + - clampDouble(minScrollExtent - pixels, 0, viewportDimension) // "below" overscroll value - - (pixels - maxScrollExtent).clamp(0, viewportDimension); + - clampDouble(pixels - maxScrollExtent, 0, viewportDimension); } /// The quantity of content conceptually "below" the viewport in the scrollable. diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index 6ce2fe6503bb..a98dfab4a9c9 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -554,7 +554,7 @@ class RangeMaintainingScrollPhysics extends ScrollPhysics { double result = super.adjustPositionForNewDimensions(oldPosition: oldPosition, newPosition: newPosition, isScrolling: isScrolling, velocity: velocity); if (enforceBoundary) { // ...but if they put us out of range then reinforce the boundary. - result = result.clamp(newPosition.minScrollExtent, newPosition.maxScrollExtent); + result = clampDouble(result, newPosition.minScrollExtent, newPosition.maxScrollExtent); } return result; } diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index 92b0c429d8e0..84a56662ef1f 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -710,16 +710,16 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { double target; switch (alignmentPolicy) { case ScrollPositionAlignmentPolicy.explicit: - target = viewport.getOffsetToReveal(object, alignment, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent); + target = clampDouble(viewport.getOffsetToReveal(object, alignment, rect: targetRect).offset, minScrollExtent, maxScrollExtent); break; case ScrollPositionAlignmentPolicy.keepVisibleAtEnd: - target = viewport.getOffsetToReveal(object, 1.0, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent); + target = clampDouble(viewport.getOffsetToReveal(object, 1.0, rect: targetRect).offset, minScrollExtent, maxScrollExtent); if (target < pixels) { target = pixels; } break; case ScrollPositionAlignmentPolicy.keepVisibleAtStart: - target = viewport.getOffsetToReveal(object, 0.0, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent); + target = clampDouble(viewport.getOffsetToReveal(object, 0.0, rect: targetRect).offset, minScrollExtent, maxScrollExtent); if (target > pixels) { target = pixels; } @@ -824,7 +824,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { assert(clamp != null); if (clamp!) - to = to.clamp(minScrollExtent, maxScrollExtent); + to = clampDouble(to, minScrollExtent, maxScrollExtent); return super.moveTo(to, duration: duration, curve: curve); } diff --git a/packages/flutter/lib/src/widgets/scroll_simulation.dart b/packages/flutter/lib/src/widgets/scroll_simulation.dart index 03732ad8ebf9..eb12592b79e7 100644 --- a/packages/flutter/lib/src/widgets/scroll_simulation.dart +++ b/packages/flutter/lib/src/widgets/scroll_simulation.dart @@ -212,13 +212,13 @@ class ClampingScrollSimulation extends Simulation { @override double x(double time) { - final double t = (time / _duration).clamp(0.0, 1.0); + final double t = clampDouble(time / _duration, 0.0, 1.0); return position + _distance * _flingDistancePenetration(t) * velocity.sign; } @override double dx(double time) { - final double t = (time / _duration).clamp(0.0, 1.0); + final double t = clampDouble(time / _duration, 0.0, 1.0); return _distance * _flingVelocityPenetration(t) * velocity.sign / _duration; } diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart index ebd9e5fe2713..9ed1b69d9cc7 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -539,8 +539,11 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { // Thumb extent reflects fraction of content visible, as long as this // isn't less than the absolute minimum size. // _totalContentExtent >= viewportDimension, so (_totalContentExtent - _mainAxisPadding) > 0 - final double fractionVisible = ((_lastMetrics!.extentInside - _mainAxisPadding) / (_totalContentExtent - _mainAxisPadding)) - .clamp(0.0, 1.0); + final double fractionVisible = clampDouble( + (_lastMetrics!.extentInside - _mainAxisPadding) / + (_totalContentExtent - _mainAxisPadding), + 0.0, + 1.0); final double thumbExtent = math.max( math.min(_trackExtent, minOverscrollLength), @@ -563,11 +566,11 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { // [0.8, 1.0] to [0.0, 1.0], so 0% to 20% of overscroll will produce // values for the thumb that range between minLength and the smallest // possible value, minOverscrollLength. - : safeMinLength * (1.0 - fractionOverscrolled.clamp(0.0, 0.2) / 0.2); + : safeMinLength * (1.0 - clampDouble(fractionOverscrolled, 0.0, 0.2) / 0.2); // The `thumbExtent` should be no greater than `trackSize`, otherwise // the scrollbar may scroll towards the wrong direction. - return thumbExtent.clamp(newMinLength, _trackExtent); + return clampDouble(thumbExtent, newMinLength, _trackExtent); } @override @@ -611,7 +614,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { final double scrollableExtent = metrics.maxScrollExtent - metrics.minScrollExtent; final double fractionPast = (scrollableExtent > 0) - ? ((metrics.pixels - metrics.minScrollExtent) / scrollableExtent).clamp(0.0, 1.0) + ? clampDouble((metrics.pixels - metrics.minScrollExtent) / scrollableExtent, 0.0, 1.0) : 0; return (_isReversed ? 1 - fractionPast : fractionPast) * (_trackExtent - thumbExtent); @@ -1608,7 +1611,7 @@ class RawScrollbarState extends State with TickerProv case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: - newPosition = newPosition.clamp(position.minScrollExtent, position.maxScrollExtent); + newPosition = clampDouble(newPosition, position.minScrollExtent, position.maxScrollExtent); break; case TargetPlatform.iOS: case TargetPlatform.android: diff --git a/packages/flutter/lib/src/widgets/sliver_fill.dart b/packages/flutter/lib/src/widgets/sliver_fill.dart index 51942d151224..d199f5fc3c81 100644 --- a/packages/flutter/lib/src/widgets/sliver_fill.dart +++ b/packages/flutter/lib/src/widgets/sliver_fill.dart @@ -60,7 +60,7 @@ class SliverFillViewport extends StatelessWidget { @override Widget build(BuildContext context) { return _SliverFractionalPadding( - viewportFraction: padEnds ? (1 - viewportFraction).clamp(0, 1) / 2 : 0, + viewportFraction: padEnds ? clampDouble(1 - viewportFraction, 0, 1) / 2 : 0, sliver: _SliverFillViewportRenderObjectWidget( viewportFraction: viewportFraction, delegate: delegate, diff --git a/packages/flutter/test/foundation/math_test.dart b/packages/flutter/test/foundation/math_test.dart new file mode 100644 index 000000000000..2c50bf766ba5 --- /dev/null +++ b/packages/flutter/test/foundation/math_test.dart @@ -0,0 +1,16 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('clampDouble', () { + expect(clampDouble(-1.0, 0.0, 1.0), equals(0.0)); + expect(clampDouble(2.0, 0.0, 1.0), equals(1.0)); + expect(clampDouble(double.infinity, 0.0, 1.0), equals(1.0)); + expect(clampDouble(-double.infinity, 0.0, 1.0), equals(0.0)); + expect(clampDouble(double.nan, 0.0, double.infinity), equals(double.infinity)); + }); +} diff --git a/packages/flutter/test_private/test/animated_icons_private_test.dart.tmpl b/packages/flutter/test_private/test/animated_icons_private_test.dart.tmpl index 482f76d35587..96d23c4d4bbd 100644 --- a/packages/flutter/test_private/test/animated_icons_private_test.dart.tmpl +++ b/packages/flutter/test_private/test/animated_icons_private_test.dart.tmpl @@ -15,6 +15,7 @@ import 'dart:math' as math show pi; import 'dart:ui' show lerpDouble, Offset; import 'dart:ui' as ui show Paint, Path, Canvas; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart';