3
3
// found in the LICENSE file.
4
4
5
5
import 'dart:collection' ;
6
+ import 'dart:math' as math show pi;
6
7
import 'dart:ui' as ui;
7
8
8
9
import 'package:flutter/foundation.dart' show Brightness, clampDouble;
@@ -279,87 +280,127 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox {
279
280
markNeedsLayout ();
280
281
}
281
282
282
- // The child is tall enough to have the arrow clipped out of it on both sides
283
- // top and bottom. Since _kToolbarHeight includes the height of one arrow, the
284
- // total height that the child is given is that plus one more arrow height.
285
- // The extra height on the opposite side of the arrow will be clipped out. By
286
- // using this approach, the buttons don't need any special padding that
287
- // depends on isAbove.
288
- final BoxConstraints _heightConstraint = BoxConstraints .tightFor (
289
- height: _kToolbarHeight + _kToolbarArrowSize.height,
290
- );
291
-
292
283
@override
293
284
void performLayout () {
285
+ final RenderBox ? child = this .child;
294
286
if (child == null ) {
295
287
return ;
296
288
}
297
289
298
- final BoxConstraints enforcedConstraint = constraints.loosen ();
290
+ // The child is tall enough to have the arrow clipped out of it on both sides
291
+ // top and bottom. Since _kToolbarHeight includes the height of one arrow, the
292
+ // total height that the child is given is that plus one more arrow height.
293
+ // The extra height on the opposite side of the arrow will be clipped out. By
294
+ // using this approach, the buttons don't need any special padding that
295
+ // depends on isAbove.
296
+ final BoxConstraints heightConstraint = BoxConstraints (
297
+ minHeight: _kToolbarHeight + _kToolbarArrowSize.height,
298
+ maxHeight: _kToolbarHeight + _kToolbarArrowSize.height,
299
+ minWidth: _kToolbarArrowSize.width + _kToolbarBorderRadius.x * 2 ,
300
+ ).enforce (constraints.loosen ());
299
301
300
- child! .layout (_heightConstraint. enforce (enforcedConstraint) , parentUsesSize: true );
302
+ child.layout (heightConstraint , parentUsesSize: true );
301
303
302
304
// The height of one arrow will be clipped off of the child, so adjust the
303
305
// size and position to remove that piece from the layout.
304
- final BoxParentData childParentData = child! .parentData! as BoxParentData ;
306
+ final BoxParentData childParentData = child.parentData! as BoxParentData ;
305
307
childParentData.offset = Offset (
306
308
0.0 ,
307
309
_isAbove ? - _kToolbarArrowSize.height : 0.0 ,
308
310
);
309
311
size = Size (
310
- child! .size.width,
311
- child! .size.height - _kToolbarArrowSize.height,
312
+ child.size.width,
313
+ child.size.height - _kToolbarArrowSize.height,
312
314
);
313
315
}
314
316
317
+ // Adds the given `rrect` to the current `path`, starting from the last point
318
+ // in `path` and ends after the last corner of the rrect (closest corner to
319
+ // `startAngle` in the counterclockwise direction), without closing the path.
320
+ //
321
+ // The `startAngle` argument must be a multiple of pi / 2, with 0 being the
322
+ // positive half of the x-axis, and pi / 2 being the negative half of the
323
+ // y-axis.
324
+ //
325
+ // For instance, if `startAngle` equals pi/2 then this method draws a line
326
+ // segment to the bottom-left corner of `rrect` from the last point in `path`,
327
+ // and follows the `rrect` path clockwise until the bottom-right corner is
328
+ // added, then this method returns the mutated path without closing it.
329
+ static Path _addRRectToPath (Path path, RRect rrect, { required double startAngle }) {
330
+ const double halfPI = math.pi / 2 ;
331
+ assert (startAngle % halfPI == 0 );
332
+ final Rect rect = rrect.outerRect;
333
+
334
+ final List <(Offset , Radius )> rrectCorners = < (Offset , Radius )> [
335
+ (rect.bottomRight, - rrect.brRadius),
336
+ (rect.bottomLeft, Radius .elliptical (rrect.blRadiusX, - rrect.blRadiusY)),
337
+ (rect.topLeft, rrect.tlRadius),
338
+ (rect.topRight, Radius .elliptical (- rrect.trRadiusX, rrect.trRadiusY)),
339
+ ];
340
+
341
+ // Add the 4 corners to the path clockwise. Convert radians to quadrants
342
+ // to avoid fp arithmetics. The order is br -> bl -> tl -> tr if the starting
343
+ // angle is 0.
344
+ final int startQuadrantIndex = startAngle ~ / halfPI;
345
+ for (int i = startQuadrantIndex; i < rrectCorners.length + startQuadrantIndex; i += 1 ) {
346
+ final (Offset vertex, Radius rectCenterOffset) = rrectCorners[i % rrectCorners.length];
347
+ final Offset otherVertex = Offset (vertex.dx + 2 * rectCenterOffset.x, vertex.dy + 2 * rectCenterOffset.y);
348
+ final Rect rect = Rect .fromPoints (vertex, otherVertex);
349
+ path.arcTo (rect, halfPI * i, halfPI, false );
350
+ }
351
+ return path;
352
+ }
353
+
315
354
// The path is described in the toolbar's coordinate system.
316
- Path _clipPath () {
317
- final BoxParentData childParentData = child ! .parentData ! as BoxParentData ;
318
- final Path rrect = Path ()
319
- .. addRRect (
320
- RRect . fromRectAndRadius (
321
- Offset ( 0.0 , _kToolbarArrowSize.height)
322
- & Size (
323
- child ! .size.width,
324
- child ! .size.height - _kToolbarArrowSize.height * 2 ,
325
- ),
326
- _kToolbarBorderRadius,
327
- ),
328
- );
355
+ Path _clipPath (RenderBox child ) {
356
+ final Rect rect = Offset ( 0.0 , _isAbove ? 0 : _kToolbarArrowSize.height)
357
+ & Size (size.width, size.height - _kToolbarArrowSize.height);
358
+ final RRect rrect = RRect . fromRectAndRadius (rect, _kToolbarBorderRadius). scaleRadii ();
359
+
360
+ final Path path = Path ();
361
+ // If there isn't enough width for the arrow + radii, ignore the arrow.
362
+ // Because of the constraints we gave children in performLayout, this should
363
+ // only happen if the parent isn't wide enough which should be very rare, and
364
+ // when that happens the arrow won't be too useful anyways.
365
+ if (_kToolbarBorderRadius.x * 2 + _kToolbarArrowSize.width > size.width) {
366
+ return path.. addRRect (rrect);
367
+ }
329
368
330
369
final Offset localAnchor = globalToLocal (_anchor);
331
- final double centerX = childParentData.offset.dx + child! .size.width / 2 ;
332
- final double arrowXOffsetFromCenter = localAnchor.dx - centerX;
333
- final double arrowTipX = child! .size.width / 2 + arrowXOffsetFromCenter;
334
-
335
- final double arrowBaseY = _isAbove
336
- ? child! .size.height - _kToolbarArrowSize.height
337
- : _kToolbarArrowSize.height;
338
-
339
- final double arrowTipY = _isAbove ? child! .size.height : 0 ;
340
-
341
- final Path arrow = Path ()
342
- ..moveTo (arrowTipX, arrowTipY)
343
- ..lineTo (arrowTipX - _kToolbarArrowSize.width / 2 , arrowBaseY)
344
- ..lineTo (arrowTipX + _kToolbarArrowSize.width / 2 , arrowBaseY)
345
- ..close ();
370
+ final double arrowTipX = clampDouble (
371
+ localAnchor.dx,
372
+ _kToolbarBorderRadius.x + _kToolbarArrowSize.width / 2 ,
373
+ size.width - _kToolbarArrowSize.width / 2 - _kToolbarBorderRadius.x,
374
+ );
346
375
347
- return Path .combine (PathOperation .union, rrect, arrow);
376
+ // Draw the path clockwise, starting from the beginning side of the arrow.
377
+ if (_isAbove) {
378
+ path
379
+ ..moveTo (arrowTipX + _kToolbarArrowSize.width / 2 , rect.bottom) // right side of the arrow triangle
380
+ ..lineTo (arrowTipX, rect.bottom + _kToolbarArrowSize.height) // The tip of the arrow
381
+ ..lineTo (arrowTipX - _kToolbarArrowSize.width / 2 , rect.bottom); // left side of the arrow triangle
382
+ } else {
383
+ path
384
+ ..moveTo (arrowTipX - _kToolbarArrowSize.width / 2 , rect.top) // right side of the arrow triangle
385
+ ..lineTo (arrowTipX, rect.top) // The tip of the arrow
386
+ ..lineTo (arrowTipX + _kToolbarArrowSize.width / 2 , rect.top); // left side of the arrow triangle
387
+ }
388
+ final double startAngle = _isAbove ? math.pi / 2 : - math.pi / 2 ;
389
+ return _addRRectToPath (path, rrect, startAngle: startAngle)..close ();
348
390
}
349
391
350
392
@override
351
393
void paint (PaintingContext context, Offset offset) {
394
+ final RenderBox ? child = this .child;
352
395
if (child == null ) {
353
396
return ;
354
397
}
355
-
356
- final BoxParentData childParentData = child! .parentData! as BoxParentData ;
357
398
_clipPathLayer.layer = context.pushClipPath (
358
399
needsCompositing,
359
- offset + childParentData.offset ,
360
- Offset .zero & child ! . size,
361
- _clipPath (),
362
- ( PaintingContext innerContext, Offset innerOffset) => innerContext. paintChild (child ! , innerOffset) ,
400
+ offset,
401
+ Offset .zero & size,
402
+ _clipPath (child ),
403
+ super .paint ,
363
404
oldLayer: _clipPathLayer.layer,
364
405
);
365
406
}
@@ -376,11 +417,12 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox {
376
417
@override
377
418
void debugPaintSize (PaintingContext context, Offset offset) {
378
419
assert (() {
420
+ final RenderBox ? child = this .child;
379
421
if (child == null ) {
380
422
return true ;
381
423
}
382
424
383
- _debugPaint ?? = Paint ()
425
+ final ui. Paint debugPaint = _debugPaint ?? = Paint ()
384
426
..shader = ui.Gradient .linear (
385
427
Offset .zero,
386
428
const Offset (10.0 , 10.0 ),
@@ -391,8 +433,8 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox {
391
433
..strokeWidth = 2.0
392
434
..style = PaintingStyle .stroke;
393
435
394
- final BoxParentData childParentData = child! .parentData! as BoxParentData ;
395
- context.canvas.drawPath (_clipPath ().shift (offset + childParentData.offset), _debugPaint ! );
436
+ final BoxParentData childParentData = child.parentData! as BoxParentData ;
437
+ context.canvas.drawPath (_clipPath (child ).shift (offset + childParentData.offset), debugPaint );
396
438
return true ;
397
439
}());
398
440
}
0 commit comments