[Cupertino] Apply RSuperellipse to most Cupertino widgets (#167784)

This PR applies RSuperellipse to most Cupertino widgets. I got the list
by searching for `ClipRRect`, `drawRRect`, and `BoxDecoration` (with
`borderRadius`) through the package. They're replaced by
`ClipRSuperellipse`, `drawRSuperellipse`, and `ShapeDecoration`
respectively.

There are a few widgets that I didn't apply:
* `CupertinoTextField` as well as its related widget
`CupertinoSearchTextField`, because `CupertinoTextField` expects a
`BoxDecoration` argument. Migrating it is nontrivial and will take place
in a separate PR.
* `CupertinoTextSelectionToolbar`, because it seems complicated
(containing a lot of paths). I wonder if it's possible to replace the
clipPath with clipRRect/clipRSe for better performance.
* `CupertinoSwitch`. I suspect it's not an squircle since it tightly
contains a full circle.

Fixes https://github.com/flutter/flutter/issues/13914.

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Tong Mu 2025-05-26 14:55:10 -07:00 committed by GitHub
parent a6205c3a66
commit df5f865a6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 250 additions and 219 deletions

View File

@ -469,7 +469,7 @@ class Tab2Header extends StatelessWidget {
child: SafeArea(
top: false,
bottom: false,
child: ClipRRect(
child: ClipRSuperellipse(
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
child: Column(
mainAxisSize: MainAxisSize.min,

View File

@ -146,7 +146,7 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
required this.activeColor,
required this.radius,
required this.progress,
}) : tickFundamentalRRect = RRect.fromLTRBXY(
}) : tickFundamentalShape = RSuperellipse.fromLTRBXY(
-radius / _kDefaultIndicatorRadius,
-radius / 3.0,
radius / _kDefaultIndicatorRadius,
@ -161,7 +161,7 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
final double radius;
final double progress;
final RRect tickFundamentalRRect;
final RSuperellipse tickFundamentalShape;
@override
void paint(Canvas canvas, Size size) {
@ -178,7 +178,7 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
paint.color = activeColor.withAlpha(
progress < 1 ? _partiallyRevealedAlpha : _kAlphaValues[t],
);
canvas.drawRRect(tickFundamentalRRect, paint);
canvas.drawRSuperellipse(tickFundamentalShape, paint);
canvas.rotate(_kTwoPI / tickCount);
}

View File

@ -510,6 +510,24 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
WidgetStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states) ??
_defaultCursor.resolve(states);
final ShapeDecoration shapeDecoration = ShapeDecoration(
shape: RoundedSuperellipseBorder(
side:
enabled && isFocused
? BorderSide(
color: effectiveFocusOutlineColor,
width: 3.5,
strokeAlign: BorderSide.strokeAlignOutside,
)
: BorderSide.none,
borderRadius: widget.borderRadius ?? kCupertinoButtonSizeBorderRadius[widget.sizeStyle],
),
color:
backgroundColor != null && !enabled
? CupertinoDynamicColor.resolve(widget.disabledColor, context)
: backgroundColor,
);
return MouseRegion(
cursor: effectiveMouseCursor,
child: FocusableActionDetector(
@ -558,24 +576,7 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
child: FadeTransition(
opacity: _opacityAnimation,
child: DecoratedBox(
decoration: BoxDecoration(
border:
enabled && isFocused
? Border.fromBorderSide(
BorderSide(
color: effectiveFocusOutlineColor,
width: 3.5,
strokeAlign: BorderSide.strokeAlignOutside,
),
)
: null,
borderRadius:
widget.borderRadius ?? kCupertinoButtonSizeBorderRadius[widget.sizeStyle],
color:
backgroundColor != null && !enabled
? CupertinoDynamicColor.resolve(widget.disabledColor, context)
: backgroundColor,
),
decoration: shapeDecoration,
child: Padding(
padding: widget.padding ?? kCupertinoButtonPadding[widget.sizeStyle]!,
child: Align(

View File

@ -191,7 +191,7 @@ class CupertinoContextMenu extends StatefulWidget {
/// animation.value < CupertinoContextMenu.animationOpensAt ? boxDecorationAnimation.value : null,
/// child: FittedBox(
/// fit: BoxFit.cover,
/// child: ClipRRect(
/// child: ClipRSuperellipse(
/// borderRadius: borderRadiusAnimation.value ?? BorderRadius.circular(0.0),
/// child: SizedBox(
/// height: 150,
@ -300,7 +300,7 @@ class CupertinoContextMenu extends StatefulWidget {
/// animation.value < CupertinoContextMenu.animationOpensAt ? boxDecorationAnimation.value : null,
/// child: FittedBox(
/// fit: BoxFit.cover,
/// child: ClipRRect(
/// child: ClipRSuperellipse(
/// borderRadius: borderRadiusAnimation.value ?? BorderRadius.circular(0.0),
/// child: SizedBox(
/// height: 150,
@ -453,7 +453,7 @@ class _CupertinoContextMenuState extends State<CupertinoContextMenu> with Ticker
) {
return FittedBox(
fit: BoxFit.cover,
child: ClipRRect(
child: ClipRSuperellipse(
borderRadius: BorderRadius.circular(_previewBorderRadiusRatio * animation.value),
child: child,
),
@ -1362,7 +1362,7 @@ class _ContextMenuSheetState extends State<_ContextMenuSheet> {
final Widget menu = SizedBox(
width: _kMenuWidth,
child: IntrinsicHeight(
child: ClipRRect(
child: ClipRSuperellipse(
borderRadius: const BorderRadius.all(Radius.circular(13.0)),
child: ColoredBox(
color: CupertinoDynamicColor.resolve(CupertinoContextMenu.kBackgroundColor, context),

View File

@ -102,9 +102,9 @@ class CupertinoDesktopTextSelectionToolbar extends StatelessWidget {
return Container(
width: _kToolbarWidth,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(
boxShadow: _kToolbarShadow,
borderRadius: BorderRadius.all(_kToolbarBorderRadius),
decoration: const ShapeDecoration(
shadows: _kToolbarShadow,
shape: RoundedSuperellipseBorder(borderRadius: BorderRadius.all(_kToolbarBorderRadius)),
),
child: BackdropFilter(
filter: ImageFilter.compose(
@ -112,11 +112,13 @@ class CupertinoDesktopTextSelectionToolbar extends StatelessWidget {
inner: ImageFilter.blur(sigmaX: _kToolbarBlurSigma, sigmaY: _kToolbarBlurSigma),
),
child: DecoratedBox(
decoration: BoxDecoration(
decoration: ShapeDecoration(
color: _kToolbarBackgroundColor.resolveFrom(context),
border: Border.all(color: _kToolbarBorderColor.resolveFrom(context)),
shape: RoundedSuperellipseBorder(
side: BorderSide(color: _kToolbarBorderColor.resolveFrom(context)),
borderRadius: const BorderRadius.all(_kToolbarBorderRadius),
),
),
child: Padding(padding: _kToolbarPadding, child: child),
),
),

View File

@ -464,12 +464,12 @@ class CupertinoListSection extends StatelessWidget {
decoratedChildrenGroup = DecoratedBox(
decoration:
decoration ??
BoxDecoration(
ShapeDecoration(
color: CupertinoDynamicColor.resolve(
decoration?.color ?? CupertinoColors.secondarySystemGroupedBackground,
context,
),
borderRadius: childrenGroupBorderRadius,
shape: RoundedSuperellipseBorder(borderRadius: childrenGroupBorderRadius),
),
child: Column(children: childrenWithDividers),
);
@ -479,7 +479,7 @@ class CupertinoListSection extends StatelessWidget {
child:
clipBehavior == Clip.none
? decoratedChildrenGroup
: ClipRRect(
: ClipRSuperellipse(
borderRadius: childrenGroupBorderRadius,
clipBehavior: clipBehavior,
child: decoratedChildrenGroup,

View File

@ -400,11 +400,13 @@ class CupertinoPickerDefaultSelectionOverlay extends StatelessWidget {
start: capStartEdge ? _defaultSelectionOverlayHorizontalMargin : 0,
end: capEndEdge ? _defaultSelectionOverlayHorizontalMargin : 0,
),
decoration: BoxDecoration(
decoration: ShapeDecoration(
shape: RoundedSuperellipseBorder(
borderRadius: BorderRadiusDirectional.horizontal(
start: capStartEdge ? radius : Radius.zero,
end: capEndEdge ? radius : Radius.zero,
),
),
color: CupertinoDynamicColor.resolve(background, context),
),
);

View File

@ -482,7 +482,7 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
}
class _SegmentedControlContainerBoxParentData extends ContainerBoxParentData<RenderBox> {
RRect? surroundingRect;
RSuperellipse? surroundingRect;
}
typedef _NextChild = RenderBox? Function(RenderBox child);
@ -630,21 +630,21 @@ class _RenderSegmentedControl<T> extends RenderBox
final Offset childOffset = Offset(start, 0.0);
childParentData.offset = childOffset;
final Rect childRect = Rect.fromLTWH(start, 0.0, child.size.width, child.size.height);
final RRect rChildRect;
final RSuperellipse rChildRect;
if (child == leftChild) {
rChildRect = RRect.fromRectAndCorners(
rChildRect = RSuperellipse.fromRectAndCorners(
childRect,
topLeft: const Radius.circular(3.0),
bottomLeft: const Radius.circular(3.0),
);
} else if (child == rightChild) {
rChildRect = RRect.fromRectAndCorners(
rChildRect = RSuperellipse.fromRectAndCorners(
childRect,
topRight: const Radius.circular(3.0),
bottomRight: const Radius.circular(3.0),
);
} else {
rChildRect = RRect.fromRectAndCorners(childRect);
rChildRect = RSuperellipse.fromRectAndCorners(childRect);
}
childParentData.surroundingRect = rChildRect;
start += child.size.width;
@ -735,13 +735,13 @@ class _RenderSegmentedControl<T> extends RenderBox
final _SegmentedControlContainerBoxParentData childParentData =
child.parentData! as _SegmentedControlContainerBoxParentData;
context.canvas.drawRRect(
context.canvas.drawRSuperellipse(
childParentData.surroundingRect!.shift(offset),
Paint()
..color = backgroundColors[childIndex]
..style = PaintingStyle.fill,
);
context.canvas.drawRRect(
context.canvas.drawRSuperellipse(
childParentData.surroundingRect!.shift(offset),
Paint()
..color = borderColor
@ -758,7 +758,7 @@ class _RenderSegmentedControl<T> extends RenderBox
while (child != null) {
final _SegmentedControlContainerBoxParentData childParentData =
child.parentData! as _SegmentedControlContainerBoxParentData;
if (childParentData.surroundingRect!.contains(position)) {
if (childParentData.surroundingRect!.outerRect.contains(position)) {
return result.addWithPaintOffset(
offset: childParentData.offset,
position: position,

View File

@ -295,7 +295,7 @@ class CupertinoSheetTransition extends StatefulWidget {
animation: radiusAnimation,
child: child,
builder: (BuildContext context, Widget? child) {
return ClipRRect(
return ClipRSuperellipse(
borderRadius:
!secondaryAnimation.isDismissed
? radiusAnimation.value
@ -333,7 +333,7 @@ class CupertinoSheetTransition extends StatefulWidget {
scale: scaleAnimation,
filterQuality: FilterQuality.medium,
alignment: Alignment.topCenter,
child: ClipRRect(
child: ClipRSuperellipse(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: child,
),
@ -505,7 +505,7 @@ class CupertinoSheetRoute<T> extends PageRoute<T> with _CupertinoSheetRouteTrans
removeTop: true,
child: Padding(
padding: EdgeInsets.only(top: topPadding),
child: ClipRRect(
child: ClipRSuperellipse(
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
child: CupertinoUserInterfaceLevel(
data: CupertinoUserInterfaceLevelData.elevated,

View File

@ -587,16 +587,16 @@ class _RenderCupertinoSlider extends RenderConstrainedBox implements MouseTracke
if (visualPosition > 0.0) {
final Paint paint = Paint()..color = rightColor;
canvas.drawRRect(
RRect.fromLTRBXY(trackLeft, trackTop, trackActive, trackBottom, 1.0, 1.0),
canvas.drawRSuperellipse(
RSuperellipse.fromLTRBXY(trackLeft, trackTop, trackActive, trackBottom, 1.0, 1.0),
paint,
);
}
if (visualPosition < 1.0) {
final Paint paint = Paint()..color = leftColor;
canvas.drawRRect(
RRect.fromLTRBXY(trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0),
canvas.drawRSuperellipse(
RSuperellipse.fromLTRBXY(trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0),
paint,
);
}

View File

@ -273,6 +273,8 @@ class _SegmentSeparatorState extends State<_SegmentSeparator>
color: _kSeparatorColor.withOpacity(
_kSeparatorColor.opacity * separatorOpacityController.value,
),
// Use RRect instead of RSuperellipse here since the radius is too
// small to make enough visual difference.
borderRadius: const BorderRadius.all(_kSeparatorRadius),
),
child: child,
@ -800,8 +802,8 @@ class _SegmentedControlState<T extends Object> extends State<CupertinoSlidingSeg
// behavior is eyeballed by the iOS 17.5 simulator.
clipBehavior: Clip.antiAlias,
padding: widget.padding.resolve(Directionality.of(context)),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(_kCornerRadius),
decoration: ShapeDecoration(
shape: const RoundedSuperellipseBorder(borderRadius: BorderRadius.all(_kCornerRadius)),
color: CupertinoDynamicColor.resolve(widget.backgroundColor, context),
),
child: AnimatedBuilder(
@ -1347,15 +1349,21 @@ class _RenderSegmentedControl<T extends Object> extends RenderBox
BoxShadow(color: Color(0x0A000000), offset: Offset(0, 3), blurRadius: 1),
];
final RRect thumbRRect = RRect.fromRectAndRadius(thumbRect.shift(offset), _kThumbRadius);
final RSuperellipse thumbShape = RSuperellipse.fromRectAndRadius(
thumbRect.shift(offset),
_kThumbRadius,
);
for (final BoxShadow shadow in thumbShadow) {
context.canvas.drawRRect(thumbRRect.shift(shadow.offset), shadow.toPaint());
context.canvas.drawRSuperellipse(thumbShape.shift(shadow.offset), shadow.toPaint());
}
context.canvas.drawRRect(thumbRRect.inflate(0.5), Paint()..color = const Color(0x0A000000));
context.canvas.drawRSuperellipse(
thumbShape.inflate(0.5),
Paint()..color = const Color(0x0A000000),
);
context.canvas.drawRRect(thumbRRect, Paint()..color = thumbColor);
context.canvas.drawRSuperellipse(thumbShape, Paint()..color = thumbColor);
}
@override

View File

@ -56,13 +56,16 @@ class CupertinoThumbPainter {
/// Consider using [radius] and [extension] when deciding how large a
/// rectangle to use for the thumb.
void paint(Canvas canvas, Rect rect) {
final RRect rrect = RRect.fromRectAndRadius(rect, Radius.circular(rect.shortestSide / 2.0));
final RSuperellipse thumbShape = RSuperellipse.fromRectAndRadius(
rect,
Radius.circular(rect.shortestSide / 2.0),
);
for (final BoxShadow shadow in shadows) {
canvas.drawRRect(rrect.shift(shadow.offset), shadow.toPaint());
canvas.drawRSuperellipse(thumbShape.shift(shadow.offset), shadow.toPaint());
}
canvas.drawRRect(rrect.inflate(0.5), Paint()..color = _kThumbBorderColor);
canvas.drawRRect(rrect, Paint()..color = color);
canvas.drawRSuperellipse(thumbShape.inflate(0.5), Paint()..color = _kThumbBorderColor);
canvas.drawRSuperellipse(thumbShape, Paint()..color = color);
}
}

View File

@ -211,7 +211,11 @@ class _RoundedRectangleToCircleBorder extends _ShapeToCircleBorder<RoundedRectan
/// * [RoundedRectangleBorder], which uses the traditional [RRect] shape.
class RoundedSuperellipseBorder extends OutlinedBorder with _RRectLikeBorder {
/// Creates a rounded rectangle border.
const RoundedSuperellipseBorder({super.side, this.borderRadius = BorderRadius.zero});
///
/// If `borderRadius` is not specified or null, it defaults to
/// [BorderRadius.zero].
const RoundedSuperellipseBorder({super.side, BorderRadiusGeometry? borderRadius})
: borderRadius = borderRadius ?? BorderRadius.zero;
/// The radii for each corner.
@override
@ -227,7 +231,7 @@ class RoundedSuperellipseBorder extends OutlinedBorder with _RRectLikeBorder {
if (a is RoundedSuperellipseBorder) {
return RoundedSuperellipseBorder(
side: BorderSide.lerp(a.side, side, t),
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
);
}
if (a is CircleBorder) {
@ -246,7 +250,7 @@ class RoundedSuperellipseBorder extends OutlinedBorder with _RRectLikeBorder {
if (b is RoundedSuperellipseBorder) {
return RoundedSuperellipseBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
);
}
if (b is CircleBorder) {
@ -272,15 +276,23 @@ class RoundedSuperellipseBorder extends OutlinedBorder with _RRectLikeBorder {
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
if (borderRadius == BorderRadius.zero) {
return Path()..addRect(rect.deflate(side.strokeInset));
} else {
final RSuperellipse borderRect = borderRadius.resolve(textDirection).toRSuperellipse(rect);
final RSuperellipse adjustedRect = borderRect.deflate(side.strokeInset);
return Path()..addRSuperellipse(adjustedRect);
}
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
if (borderRadius == BorderRadius.zero) {
return Path()..addRect(rect);
} else {
return Path()..addRSuperellipse(borderRadius.resolve(textDirection).toRSuperellipse(rect));
}
}
@override
void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) {
@ -300,13 +312,11 @@ class RoundedSuperellipseBorder extends OutlinedBorder with _RRectLikeBorder {
case BorderStyle.none:
break;
case BorderStyle.solid:
if (side.width == 0.0) {
canvas.drawRSuperellipse(
borderRadius.resolve(textDirection).toRSuperellipse(rect),
side.toPaint(),
);
} else {
final double strokeOffset = (side.strokeOutset - side.strokeInset) / 2;
if (borderRadius == BorderRadius.zero) {
final Rect base = rect.inflate(strokeOffset);
canvas.drawRect(base, side.toPaint());
} else {
final RSuperellipse base = borderRadius
.resolve(textDirection)
.toRSuperellipse(rect)

View File

@ -126,7 +126,9 @@ void main() {
// first tick was changed to be at 12 o'clock.
expect(
find.byType(CupertinoActivityIndicator),
paints..rrect(rrect: const RRect.fromLTRBXY(-10, -100 / 3, 10, -100, 10, 10)),
paints..rsuperellipse(
rsuperellipse: const RSuperellipse.fromLTRBXY(-10, -100 / 3, 10, -100, 10, 10),
),
);
});
@ -150,8 +152,8 @@ void main() {
expect(
find.byType(CupertinoActivityIndicator),
paints..rrect(
rrect: const RRect.fromLTRBXY(-10, -100 / 3, 10, -100, 10, 10),
paints..rsuperellipse(
rsuperellipse: const RSuperellipse.fromLTRBXY(-10, -100 / 3, 10, -100, 10, 10),
color: const Color(0x935d3fd3),
),
);

View File

@ -379,11 +379,11 @@ void main() {
),
);
BoxDecoration boxDecoration =
ShapeDecoration decoration =
tester.widget<DecoratedBox>(find.widgetWithText(DecoratedBox, 'Skeuomorph me')).decoration
as BoxDecoration;
as ShapeDecoration;
expect(boxDecoration.color, const Color(0x000000FF));
expect(decoration.color, const Color(0x000000FF));
await tester.pumpWidget(
boilerplate(
@ -396,11 +396,11 @@ void main() {
),
);
boxDecoration =
decoration =
tester.widget<DecoratedBox>(find.widgetWithText(DecoratedBox, 'Skeuomorph me')).decoration
as BoxDecoration;
as ShapeDecoration;
expect(boxDecoration.color, const Color(0x0000FF00));
expect(decoration.color, const Color(0x0000FF00));
});
testWidgets('Can specify dynamic colors', (WidgetTester tester) async {
@ -428,11 +428,11 @@ void main() {
),
);
BoxDecoration boxDecoration =
ShapeDecoration decoration =
tester.widget<DecoratedBox>(find.widgetWithText(DecoratedBox, 'Skeuomorph me')).decoration
as BoxDecoration;
as ShapeDecoration;
expect(boxDecoration.color!.value, 0xFF654321);
expect(decoration.color!.value, 0xFF654321);
await tester.pumpWidget(
MediaQuery(
@ -448,12 +448,12 @@ void main() {
),
);
boxDecoration =
decoration =
tester.widget<DecoratedBox>(find.widgetWithText(DecoratedBox, 'Skeuomorph me')).decoration
as BoxDecoration;
as ShapeDecoration;
// Disabled color.
expect(boxDecoration.color!.value, 0xFF111111);
expect(decoration.color!.value, 0xFF111111);
});
testWidgets('Button respects themes', (WidgetTester tester) async {
@ -488,7 +488,7 @@ void main() {
),
);
expect(textStyle.color, CupertinoColors.activeBlue);
BoxDecoration decoration =
ShapeDecoration decoration =
tester
.widget<DecoratedBox>(
find.descendant(
@ -497,7 +497,7 @@ void main() {
),
)
.decoration
as BoxDecoration;
as ShapeDecoration;
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue.withOpacity(0.12)));
await tester.pumpWidget(
@ -523,7 +523,7 @@ void main() {
),
)
.decoration
as BoxDecoration;
as ShapeDecoration;
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue));
await tester.pumpWidget(
@ -566,7 +566,7 @@ void main() {
),
)
.decoration
as BoxDecoration;
as ShapeDecoration;
expect(decoration.color, isSameColorAs(CupertinoColors.activeBlue.darkColor.withOpacity(0.26)));
await tester.pumpWidget(
@ -593,7 +593,7 @@ void main() {
),
)
.decoration
as BoxDecoration;
as ShapeDecoration;
expect(decoration.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
await tester.pumpWidget(
@ -620,7 +620,7 @@ void main() {
),
)
.decoration
as BoxDecoration;
as ShapeDecoration;
expect(decoration.color, isSameColorAs(CupertinoColors.systemRed));
});
@ -667,8 +667,7 @@ void main() {
final FocusNode focusNode = FocusNode(debugLabel: 'Button');
addTearDown(focusNode.dispose);
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final Border defaultFocusBorder = Border.fromBorderSide(
BorderSide(
final BorderSide defaultFocusBorder = BorderSide(
color:
HSLColor.fromColor(CupertinoColors.activeBlue.withOpacity(kCupertinoFocusColorOpacity))
.withLightness(kCupertinoFocusColorBrightness)
@ -676,7 +675,6 @@ void main() {
.toColor(),
width: 3.5,
strokeAlign: BorderSide.strokeAlignOutside,
),
);
await tester.pumpWidget(
@ -695,33 +693,25 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue);
// The button has no border.
final BoxDecoration unfocusedDecoration =
tester
.widget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoButton),
matching: find.byType(DecoratedBox),
expect(
_findBorder(
tester,
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(DecoratedBox)),
),
)
.decoration
as BoxDecoration;
BorderSide.none,
);
await tester.pump();
expect(unfocusedDecoration.border, null);
// When focused, the button has a light blue border outline by default.
focusNode.requestFocus();
await tester.pumpAndSettle();
final BoxDecoration decoration =
tester
.widget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoButton),
matching: find.byType(DecoratedBox),
expect(
_findBorder(
tester,
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(DecoratedBox)),
),
)
.decoration
as BoxDecoration;
expect(decoration.border, defaultFocusBorder);
defaultFocusBorder,
);
});
testWidgets('Button configures focus color', (WidgetTester tester) async {
@ -748,22 +738,12 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue);
focusNode.requestFocus();
await tester.pump();
final BoxDecoration decoration =
tester
.widget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoButton),
matching: find.byType(DecoratedBox),
),
)
.decoration
as BoxDecoration;
final Border border = decoration.border! as Border;
await tester.pumpAndSettle();
expect(border.top.color, focusColor);
expect(border.left.color, focusColor);
expect(border.right.color, focusColor);
expect(border.bottom.color, focusColor);
final BorderSide borderSide = _findBorder(
tester,
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(DecoratedBox)),
);
expect(borderSide.color, focusColor);
});
testWidgets('CupertinoButton.onFocusChange callback', (WidgetTester tester) async {
@ -1051,3 +1031,9 @@ class _ButtonMouseCursor extends WidgetStateMouseCursor {
@override
String get debugDescription => '_ButtonMouseCursor()';
}
BorderSide _findBorder(WidgetTester tester, Finder finder) {
final ShapeDecoration decoration =
tester.widget<DecoratedBox>(finder).decoration as ShapeDecoration;
return (decoration.shape as RoundedSuperellipseBorder).side;
}

View File

@ -104,7 +104,7 @@ void main() {
}
Finder findStaticDefaultPreview() {
return find.descendant(of: findFittedBox(), matching: find.byType(ClipRRect));
return find.descendant(of: findFittedBox(), matching: find.byType(ClipRSuperellipse));
}
group('CupertinoContextMenu before and during opening', () {
@ -698,7 +698,8 @@ void main() {
// Check border radius.
expect(findStaticDefaultPreview(), findsOneWidget);
final ClipRRect previewWidget = tester.firstWidget(findStaticDefaultPreview()) as ClipRRect;
final ClipRSuperellipse previewWidget =
tester.firstWidget(findStaticDefaultPreview()) as ClipRSuperellipse;
expect(previewWidget.borderRadius, equals(BorderRadius.circular(12.0)));
});

View File

@ -68,7 +68,7 @@ void main() {
),
);
expect((decoratedBox.decoration as BoxDecoration).boxShadow, isNotNull);
expect((decoratedBox.decoration as ShapeDecoration).shadows, isNotNull);
});
testWidgets('is translucent', (WidgetTester tester) async {
@ -98,7 +98,7 @@ void main() {
// The second DecoratedBox should be the one with color.
.elementAt(1);
expect((decoratedBox.decoration as BoxDecoration).color!.opacity, lessThan(1.0));
expect((decoratedBox.decoration as ShapeDecoration).color!.opacity, lessThan(1.0));
});
testWidgets('positions itself at the anchor', (WidgetTester tester) async {

View File

@ -140,10 +140,10 @@ void main() {
),
);
expect(find.byType(ClipRRect), findsOneWidget);
expect(find.byType(ClipRSuperellipse), findsOneWidget);
});
testWidgets('Not setting clipBehavior does not produce a RenderClipRRect object', (
testWidgets('Not setting clipBehavior does not produce a RenderClipRSuperellipse object', (
WidgetTester tester,
) async {
await tester.pumpWidget(
@ -152,8 +152,8 @@ void main() {
),
);
final Iterable<RenderClipRRect> renderClips =
tester.allRenderObjects.whereType<RenderClipRRect>();
final Iterable<RenderClipRSuperellipse> renderClips =
tester.allRenderObjects.whereType<RenderClipRSuperellipse>();
expect(renderClips, isEmpty);
});

View File

@ -150,7 +150,7 @@ void main() {
),
);
expect(find.byType(ClipRRect), findsOneWidget);
expect(find.byType(ClipRSuperellipse), findsOneWidget);
});
testWidgets('not setting clipBehavior does not clip children section', (
@ -166,7 +166,7 @@ void main() {
),
);
expect(find.byType(ClipRRect), findsNothing);
expect(find.byType(ClipRSuperellipse), findsNothing);
});
testWidgets('CupertinoListSection respects separatorColor', (WidgetTester tester) async {

View File

@ -189,7 +189,7 @@ void main() {
expect(
find.byType(CupertinoPicker),
paints..rrect(color: const Color.fromARGB(30, 118, 118, 128)),
paints..rsuperellipse(color: const Color.fromARGB(30, 118, 118, 128)),
);
expect(find.byType(CupertinoPicker), paints..rect(color: const Color(0xFF123456)));
@ -217,7 +217,7 @@ void main() {
expect(
find.byType(CupertinoPicker),
paints..rrect(color: const Color.fromARGB(61, 118, 118, 128)),
paints..rsuperellipse(color: const Color.fromARGB(61, 118, 118, 128)),
);
expect(find.byType(CupertinoPicker), paints..rect(color: const Color(0xFF654321)));
});
@ -244,7 +244,7 @@ void main() {
),
);
expect(find.byType(CupertinoPicker), paints..rrect(color: const Color(0x12345678)));
expect(find.byType(CupertinoPicker), paints..rsuperellipse(color: const Color(0x12345678)));
});
testWidgets('CupertinoPicker.selectionOverlay is nullable', (WidgetTester tester) async {
@ -267,7 +267,7 @@ void main() {
),
);
expect(find.byType(CupertinoPicker), isNot(paints..rrect()));
expect(find.byType(CupertinoPicker), isNot(paints..rsuperellipse()));
});
group('scroll', () {
@ -564,7 +564,8 @@ void main() {
final Container container = tester.firstWidget<Container>(selectionContainer);
final EdgeInsetsGeometry? margin = container.margin;
final BorderRadiusGeometry? borderRadius =
(container.decoration as BoxDecoration?)?.borderRadius;
((container.decoration as ShapeDecoration?)?.shape as RoundedSuperellipseBorder?)
?.borderRadius;
expect(margin, isA<EdgeInsetsDirectional>());
expect(borderRadius, isA<BorderRadiusDirectional>());

View File

@ -58,7 +58,7 @@ int getChildCount(WidgetTester tester) {
.length;
}
ui.RRect getSurroundingRect(WidgetTester tester, {int child = 0}) {
ui.RSuperellipse getSurroundingShape(WidgetTester tester, {int child = 0}) {
return ((getRenderSegmentedControl(tester)
as RenderBoxContainerDefaultsMixin<
RenderBox,
@ -68,7 +68,7 @@ ui.RRect getSurroundingRect(WidgetTester tester, {int child = 0}) {
.parentData!
as dynamic)
.surroundingRect
as ui.RRect;
as ui.RSuperellipse;
}
Size getChildSize(WidgetTester tester, {int child = 0}) {
@ -703,9 +703,9 @@ void main() {
expect(childWidth, 200.0);
expect(childWidth, getSurroundingRect(tester).width);
expect(childWidth, getSurroundingRect(tester, child: 1).width);
expect(childWidth, getSurroundingRect(tester, child: 2).width);
expect(childWidth, getSurroundingShape(tester).width);
expect(childWidth, getSurroundingShape(tester, child: 1).width);
expect(childWidth, getSurroundingShape(tester, child: 2).width);
});
testWidgets('Width is finite in unbounded space', (WidgetTester tester) async {

View File

@ -811,8 +811,8 @@ void main() {
await tester.tap(find.byType(Icon));
await tester.pumpAndSettle();
final Finder clipRRectFinder = find.byType(ClipRRect);
expect(clipRRectFinder, findsNothing);
expect(find.byType(ClipRSuperellipse), findsNothing);
expect(find.byType(ClipRRect), findsNothing);
});
testWidgets('Sheet transition does not interfere after popping', (WidgetTester tester) async {

View File

@ -569,7 +569,7 @@ void main() {
expect(
find.byType(CupertinoSlider),
// First line it paints is blue.
paints..rrect(color: CupertinoColors.systemBlue.color),
paints..rsuperellipse(color: CupertinoColors.systemBlue.color),
);
await tester.pumpWidget(
@ -581,7 +581,7 @@ void main() {
expect(
find.byType(CupertinoSlider),
paints..rrect(color: CupertinoColors.systemBlue.darkColor),
paints..rsuperellipse(color: CupertinoColors.systemBlue.darkColor),
);
});
@ -600,7 +600,7 @@ void main() {
);
expect(
find.byType(CupertinoSlider),
paints..rrect(color: CupertinoColors.systemGreen.darkColor),
paints..rsuperellipse(color: CupertinoColors.systemGreen.darkColor),
);
});
@ -642,24 +642,30 @@ void main() {
await tester.pumpWidget(
CupertinoApp(home: withTraits(Brightness.light, CupertinoUserInterfaceLevelData.base, false)),
);
expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.color));
expect(find.byType(CupertinoSlider), paints..rsuperellipse(color: activeColor.color));
await tester.pumpWidget(
CupertinoApp(home: withTraits(Brightness.dark, CupertinoUserInterfaceLevelData.base, false)),
);
expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.darkColor));
expect(find.byType(CupertinoSlider), paints..rsuperellipse(color: activeColor.darkColor));
await tester.pumpWidget(
CupertinoApp(
home: withTraits(Brightness.dark, CupertinoUserInterfaceLevelData.elevated, false),
),
);
expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.darkElevatedColor));
expect(
find.byType(CupertinoSlider),
paints..rsuperellipse(color: activeColor.darkElevatedColor),
);
await tester.pumpWidget(
CupertinoApp(home: withTraits(Brightness.dark, CupertinoUserInterfaceLevelData.base, true)),
);
expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.darkHighContrastColor));
expect(
find.byType(CupertinoSlider),
paints..rsuperellipse(color: activeColor.darkHighContrastColor),
);
await tester.pumpWidget(
CupertinoApp(
@ -668,20 +674,23 @@ void main() {
);
expect(
find.byType(CupertinoSlider),
paints..rrect(color: activeColor.darkHighContrastElevatedColor),
paints..rsuperellipse(color: activeColor.darkHighContrastElevatedColor),
);
await tester.pumpWidget(
CupertinoApp(home: withTraits(Brightness.light, CupertinoUserInterfaceLevelData.base, true)),
);
expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.highContrastColor));
expect(
find.byType(CupertinoSlider),
paints..rsuperellipse(color: activeColor.highContrastColor),
);
await tester.pumpWidget(
CupertinoApp(
home: withTraits(Brightness.light, CupertinoUserInterfaceLevelData.elevated, false),
),
);
expect(find.byType(CupertinoSlider), paints..rrect(color: activeColor.elevatedColor));
expect(find.byType(CupertinoSlider), paints..rsuperellipse(color: activeColor.elevatedColor));
await tester.pumpWidget(
CupertinoApp(
@ -690,7 +699,7 @@ void main() {
);
expect(
find.byType(CupertinoSlider),
paints..rrect(color: activeColor.highContrastElevatedColor),
paints..rsuperellipse(color: activeColor.highContrastElevatedColor),
);
});
@ -708,9 +717,12 @@ void main() {
),
);
expect(find.byType(CupertinoSlider), paints..rrect(color: _kSystemFill.color));
expect(find.byType(CupertinoSlider), paints..rsuperellipse(color: _kSystemFill.color));
expect(find.byType(CupertinoSlider), isNot(paints..rrect(color: _kSystemFill.darkColor)));
expect(
find.byType(CupertinoSlider),
isNot(paints..rsuperellipse(color: _kSystemFill.darkColor)),
);
await tester.pumpWidget(
CupertinoApp(
@ -725,9 +737,9 @@ void main() {
),
);
expect(find.byType(CupertinoSlider), paints..rrect(color: _kSystemFill.darkColor));
expect(find.byType(CupertinoSlider), paints..rsuperellipse(color: _kSystemFill.darkColor));
expect(find.byType(CupertinoSlider), isNot(paints..rrect(color: _kSystemFill.color)));
expect(find.byType(CupertinoSlider), isNot(paints..rsuperellipse(color: _kSystemFill.color)));
});
testWidgets('Thumb color can be overridden', (WidgetTester tester) async {
@ -746,12 +758,12 @@ void main() {
expect(
find.byType(CupertinoSlider),
paints
..rrect()
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: CupertinoColors.systemPurple.color),
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse(color: CupertinoColors.systemPurple.color),
);
await tester.pumpWidget(
@ -769,12 +781,12 @@ void main() {
expect(
find.byType(CupertinoSlider),
paints
..rrect()
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: CupertinoColors.activeOrange.color),
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse(color: CupertinoColors.activeOrange.color),
);
});

View File

@ -324,7 +324,7 @@ void main() {
),
);
final BoxDecoration decoration =
final ShapeDecoration decoration =
tester
.widget<Container>(
find.descendant(
@ -333,7 +333,7 @@ void main() {
),
)
.decoration!
as BoxDecoration;
as ShapeDecoration;
expect(getThumbColor(tester).value, CupertinoColors.systemGreen.color.value);
expect(decoration.color!.value, CupertinoColors.systemRed.color.value);
@ -343,7 +343,7 @@ void main() {
});
await tester.pump();
final BoxDecoration decorationDark =
final ShapeDecoration decorationDark =
tester
.widget<Container>(
find.descendant(
@ -352,7 +352,7 @@ void main() {
),
)
.decoration!
as BoxDecoration;
as ShapeDecoration;
expect(getThumbColor(tester).value, CupertinoColors.systemGreen.darkColor.value);
expect(decorationDark.color!.value, CupertinoColors.systemRed.darkColor.value);

View File

@ -42,8 +42,8 @@ void main() {
DecoratedBox decoratedBox = tester.widget(
find.descendant(of: find.byType(CupertinoButton), matching: find.byType(DecoratedBox)),
);
BoxDecoration boxDecoration = decoratedBox.decoration as BoxDecoration;
expect(boxDecoration.color, CupertinoColors.transparent);
ShapeDecoration decoration = decoratedBox.decoration as ShapeDecoration;
expect(decoration.color, CupertinoColors.transparent);
// Make a "down" gesture on the button.
final Offset center = tester.getCenter(find.byType(CupertinoTextSelectionToolbarButton));
@ -57,8 +57,8 @@ void main() {
matching: find.byType(DecoratedBox),
),
);
boxDecoration = decoratedBox.decoration as BoxDecoration;
expect(boxDecoration.color!.value, const Color(0x10000000).value);
decoration = decoratedBox.decoration as ShapeDecoration;
expect(decoration.color!.value, const Color(0x10000000).value);
// Release the down gesture.
await gesture.up();
@ -71,8 +71,8 @@ void main() {
matching: find.byType(DecoratedBox),
),
);
boxDecoration = decoratedBox.decoration as BoxDecoration;
expect(boxDecoration.color, CupertinoColors.transparent);
decoration = decoratedBox.decoration as ShapeDecoration;
expect(decoration.color, CupertinoColors.transparent);
});
testWidgets('passing null to onPressed disables the button', (WidgetTester tester) async {

View File

@ -1162,8 +1162,8 @@ void main() {
expect(
find.byType(CupertinoActivityIndicator),
paints..rrect(
rrect: const RRect.fromLTRBXY(-1, -10 / 3, 1, -10, 1, 1),
paints..rsuperellipse(
rsuperellipse: const RSuperellipse.fromLTRBXY(-1, -10 / 3, 1, -10, 1, 1),
color: const Color(0x935D3FD3),
),
);

View File

@ -3572,12 +3572,12 @@ void main() {
expect(
material,
paints
..rrect()
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: CupertinoColors.white),
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse(color: CupertinoColors.white),
);
});
@ -3600,12 +3600,12 @@ void main() {
expect(
material,
paints
..rrect()
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: color),
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse()
..rsuperellipse(color: color),
);
});

View File

@ -796,7 +796,10 @@ void main() {
expect(find.text('Page One'), findsOneWidget);
final Finder cupertinoSheetDelegatedTransitionFinder = find.ancestor(
of: find.ancestor(of: find.byType(ClipRRect), matching: find.byType(AnimatedBuilder)),
of: find.ancestor(
of: find.byType(ClipRSuperellipse),
matching: find.byType(AnimatedBuilder),
),
matching: find.byType(ScaleTransition),
);
expect(cupertinoSheetDelegatedTransitionFinder, findsOneWidget);