Unverified Commit 946a1291 authored by xubaolin's avatar xubaolin Committed by GitHub

Update [ToggleButtons] to support extend down/up vertically (#70670)

parent 60bdcf55
......@@ -15,9 +15,9 @@ import 'theme.dart';
import 'theme_data.dart';
import 'toggle_buttons_theme.dart';
/// A horizontal set of toggle buttons.
/// A set of toggle buttons.
///
/// The list of [children] are laid out in a row. The state of each button
/// The list of [children] are laid out along [direction]. The state of each button
/// is controlled by [isSelected], which is a list of bools that determine
/// if a button is in an unselected or selected state. They are both
/// correlated by their index in the list. The length of [isSelected] has to
......@@ -150,9 +150,9 @@ import 'toggle_buttons_theme.dart';
///
/// * <https://material.io/design/components/buttons.html#toggle-button>
class ToggleButtons extends StatelessWidget {
/// Creates a horizontal set of toggle buttons.
/// Creates a set of toggle buttons.
///
/// It displays its widgets provided in a [List] of [children] horizontally.
/// It displays its widgets provided in a [List] of [children] along [direction].
/// The state of each button is controlled by [isSelected], which is a list
/// of bools that determine if a button is in an active, disabled, or
/// selected state. They are both correlated by their index in the list.
......@@ -162,7 +162,8 @@ class ToggleButtons extends StatelessWidget {
/// Both [children] and [isSelected] properties arguments are required.
///
/// [isSelected] values must be non-null. [focusNodes] must be null or a
/// list of non-null nodes. [renderBorder] must not be null.
/// list of non-null nodes. [renderBorder] and [direction] must not be null.
/// If [direction] is [Axis.vertical], [verticalDirection] must not be null.
const ToggleButtons({
Key? key,
required this.children,
......@@ -186,10 +187,14 @@ class ToggleButtons extends StatelessWidget {
this.disabledBorderColor,
this.borderRadius,
this.borderWidth,
this.direction = Axis.horizontal,
this.verticalDirection = VerticalDirection.down,
}) :
assert(children != null),
assert(isSelected != null),
assert(children.length == isSelected.length),
assert(direction != null),
assert(direction == Axis.horizontal || verticalDirection != null),
super(key: key);
static const double _defaultBorderWidth = 1.0;
......@@ -382,14 +387,31 @@ class ToggleButtons extends StatelessWidget {
/// the buttons default to non-rounded borders.
final BorderRadius? borderRadius;
bool _isFirstIndex(int index, int length, TextDirection textDirection) {
return index == 0 && textDirection == TextDirection.ltr
|| index == length - 1 && textDirection == TextDirection.rtl;
/// The direction along which the buttons are rendered.
///
/// Defaults to [Axis.horizontal].
final Axis direction;
/// If [direction] is [Axis.vertical], this parameter determines whether to lay out
/// the buttons starting from the first or last child from top to bottom.
final VerticalDirection verticalDirection;
// Determines if this is the first child that is being laid out
// by the render object, _not_ the order of the children in its list.
bool _isFirstButton(int index, int length, TextDirection textDirection) {
return index == 0 && ((direction == Axis.horizontal && textDirection == TextDirection.ltr) ||
(direction == Axis.vertical && verticalDirection == VerticalDirection.down))
|| index == length - 1 && ((direction == Axis.horizontal && textDirection == TextDirection.rtl) ||
(direction == Axis.vertical && verticalDirection == VerticalDirection.up));
}
bool _isLastIndex(int index, int length, TextDirection textDirection) {
return index == length - 1 && textDirection == TextDirection.ltr
|| index == 0 && textDirection == TextDirection.rtl;
// Determines if this is the last child that is being laid out
// by the render object, _not_ the order of the children in its list.
bool _isLastButton(int index, int length, TextDirection textDirection) {
return index == length - 1 && ((direction == Axis.horizontal && textDirection == TextDirection.ltr) ||
(direction == Axis.vertical && verticalDirection == VerticalDirection.down))
|| index == 0 && ((direction == Axis.horizontal && textDirection == TextDirection.rtl) ||
(direction == Axis.vertical && verticalDirection == VerticalDirection.up));
}
BorderRadius _getEdgeBorderRadius(
......@@ -402,17 +424,32 @@ class ToggleButtons extends StatelessWidget {
?? toggleButtonsTheme.borderRadius
?? BorderRadius.zero;
if (_isFirstIndex(index, length, textDirection)) {
if (direction == Axis.horizontal) {
if (_isFirstButton(index, length, textDirection)) {
return BorderRadius.only(
topLeft: resultingBorderRadius.topLeft,
bottomLeft: resultingBorderRadius.bottomLeft,
);
} else if (_isLastIndex(index, length, textDirection)) {
} else if (_isLastButton(index, length, textDirection)) {
return BorderRadius.only(
topRight: resultingBorderRadius.topRight,
bottomRight: resultingBorderRadius.bottomRight,
);
}
} else {
if (_isFirstButton(index, length, textDirection)) {
return BorderRadius.only(
topLeft: resultingBorderRadius.topLeft,
topRight: resultingBorderRadius.topRight,
);
} else if (_isLastButton(index, length, textDirection)) {
return BorderRadius.only(
bottomLeft: resultingBorderRadius.bottomLeft,
bottomRight: resultingBorderRadius.bottomRight,
);
}
}
return BorderRadius.zero;
}
......@@ -429,17 +466,31 @@ class ToggleButtons extends StatelessWidget {
?? toggleButtonsTheme.borderWidth
?? _defaultBorderWidth;
if (_isFirstIndex(index, length, textDirection)) {
if (direction == Axis.horizontal) {
if (_isFirstButton(index, length, textDirection)) {
return BorderRadius.only(
topLeft: resultingBorderRadius.topLeft - Radius.circular(resultingBorderWidth / 2.0),
bottomLeft: resultingBorderRadius.bottomLeft - Radius.circular(resultingBorderWidth / 2.0),
);
} else if (_isLastIndex(index, length, textDirection)) {
} else if (_isLastButton(index, length, textDirection)) {
return BorderRadius.only(
topRight: resultingBorderRadius.topRight - Radius.circular(resultingBorderWidth / 2.0),
bottomRight: resultingBorderRadius.bottomRight - Radius.circular(resultingBorderWidth / 2.0),
);
}
} else {
if (_isFirstButton(index, length, textDirection)) {
return BorderRadius.only(
topLeft: resultingBorderRadius.topLeft - Radius.circular(resultingBorderWidth / 2.0),
topRight: resultingBorderRadius.topRight - Radius.circular(resultingBorderWidth / 2.0),
);
} else if (_isLastButton(index, length, textDirection)) {
return BorderRadius.only(
bottomLeft: resultingBorderRadius.bottomLeft - Radius.circular(resultingBorderWidth / 2.0),
bottomRight: resultingBorderRadius.bottomRight - Radius.circular(resultingBorderWidth / 2.0),
);
}
}
return BorderRadius.zero;
}
......@@ -478,7 +529,7 @@ class ToggleButtons extends StatelessWidget {
}
}
BorderSide _getHorizontalBorderSide(
BorderSide _getBorderSide(
int index,
ThemeData theme,
ToggleButtonsThemeData toggleButtonsTheme,
......@@ -579,16 +630,12 @@ class ToggleButtons extends StatelessWidget {
final ToggleButtonsThemeData toggleButtonsTheme = ToggleButtonsTheme.of(context);
final TextDirection textDirection = Directionality.of(context);
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(children.length, (int index) {
final List<Widget> buttons = List<Widget>.generate(children.length, (int index) {
final BorderRadius edgeBorderRadius = _getEdgeBorderRadius(index, children.length, textDirection, toggleButtonsTheme);
final BorderRadius clipBorderRadius = _getClipBorderRadius(index, children.length, textDirection, toggleButtonsTheme);
final BorderSide leadingBorderSide = _getLeadingBorderSide(index, theme, toggleButtonsTheme);
final BorderSide horizontalBorderSide = _getHorizontalBorderSide(index, theme, toggleButtonsTheme);
final BorderSide borderSide = _getBorderSide(index, theme, toggleButtonsTheme);
final BorderSide trailingBorderSide = _getTrailingBorderSide(index, theme, toggleButtonsTheme);
return _ToggleButton(
......@@ -605,19 +652,36 @@ class ToggleButtons extends StatelessWidget {
splashColor: splashColor ?? toggleButtonsTheme.splashColor,
focusNode: focusNodes != null ? focusNodes![index] : null,
onPressed: onPressed != null
? () { onPressed!(index); }
? () {onPressed!(index);}
: null,
mouseCursor: mouseCursor,
leadingBorderSide: leadingBorderSide,
horizontalBorderSide: horizontalBorderSide,
borderSide: borderSide,
trailingBorderSide: trailingBorderSide,
borderRadius: edgeBorderRadius,
clipRadius: clipBorderRadius,
isFirstButton: index == 0,
isLastButton: index == children.length - 1,
direction: direction,
verticalDirection: verticalDirection,
child: children[index],
);
}),
});
return direction == Axis.horizontal
? IntrinsicHeight(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: buttons,
),
)
: IntrinsicWidth(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
verticalDirection: verticalDirection,
children: buttons,
),
);
}
......@@ -644,6 +708,8 @@ class ToggleButtons extends StatelessWidget {
properties.add(ColorProperty('disabledBorderColor', disabledBorderColor, defaultValue: null));
properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius, defaultValue: null));
properties.add(DoubleProperty('borderWidth', borderWidth, defaultValue: null));
properties.add(DiagnosticsProperty<Axis>('direction', direction, defaultValue: Axis.horizontal));
properties.add(DiagnosticsProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
}
}
......@@ -676,12 +742,14 @@ class _ToggleButton extends StatelessWidget {
this.onPressed,
this.mouseCursor,
required this.leadingBorderSide,
required this.horizontalBorderSide,
required this.borderSide,
required this.trailingBorderSide,
required this.borderRadius,
required this.clipRadius,
required this.isFirstButton,
required this.isLastButton,
required this.direction,
required this.verticalDirection,
required this.child,
}) : super(key: key);
......@@ -740,8 +808,12 @@ class _ToggleButton extends StatelessWidget {
/// The width and color of the button's leading side border.
final BorderSide leadingBorderSide;
/// The width and color of the button's top and bottom side borders.
final BorderSide horizontalBorderSide;
/// If [direction] is [Axis.horizontal], this corresponds the width and color
/// of the button's top and bottom side borders.
///
/// If [direction] is [Axis.vertical], this corresponds the width and color
/// of the button's left and right side borders.
final BorderSide borderSide;
/// The width and color of the button's trailing side border.
final BorderSide trailingBorderSide;
......@@ -761,6 +833,13 @@ class _ToggleButton extends StatelessWidget {
/// Whether or not this toggle button is the last button in the list.
final bool isLastButton;
/// The direction along which the buttons are rendered.
final Axis direction;
/// If [direction] is [Axis.vertical], this property defines whether or not this button in its list
/// of buttons is laid out starting from top to bottom or from bottom to top.
final VerticalDirection verticalDirection;
/// The button's label, which is usually an [Icon] or a [Text] widget.
final Widget child;
......@@ -840,11 +919,13 @@ class _ToggleButton extends StatelessWidget {
return _SelectToggleButton(
key: key,
leadingBorderSide: leadingBorderSide,
horizontalBorderSide: horizontalBorderSide,
borderSide: borderSide,
trailingBorderSide: trailingBorderSide,
borderRadius: borderRadius,
isFirstButton: isFirstButton,
isLastButton: isLastButton,
direction: direction,
verticalDirection: verticalDirection,
child: result,
);
}
......@@ -865,11 +946,13 @@ class _SelectToggleButton extends SingleChildRenderObjectWidget {
Key? key,
required Widget child,
required this.leadingBorderSide,
required this.horizontalBorderSide,
required this.borderSide,
required this.trailingBorderSide,
required this.borderRadius,
required this.isFirstButton,
required this.isLastButton,
required this.direction,
required this.verticalDirection,
}) : super(
key: key,
child: child,
......@@ -878,8 +961,14 @@ class _SelectToggleButton extends SingleChildRenderObjectWidget {
// The width and color of the button's leading side border.
final BorderSide leadingBorderSide;
// The width and color of the button's top and bottom side borders.
final BorderSide horizontalBorderSide;
// The width and color of the side borders.
//
// If [direction] is [Axis.horizontal], this corresponds to the width and color
// of the button's top and bottom side borders.
//
// If [direction] is [Axis.vertical], this corresponds to the width and color
// of the button's left and right side borders.
final BorderSide borderSide;
// The width and color of the button's trailing side border.
final BorderSide trailingBorderSide;
......@@ -893,14 +982,23 @@ class _SelectToggleButton extends SingleChildRenderObjectWidget {
// Whether or not this toggle button is the last button in the list.
final bool isLastButton;
// The direction along which the buttons are rendered.
final Axis direction;
// If [direction] is [Axis.vertical], this property defines whether or not this button in its list
// of buttons is laid out starting from top to bottom or from bottom to top.
final VerticalDirection verticalDirection;
@override
_SelectToggleButtonRenderObject createRenderObject(BuildContext context) => _SelectToggleButtonRenderObject(
leadingBorderSide,
horizontalBorderSide,
borderSide,
trailingBorderSide,
borderRadius,
isFirstButton,
isLastButton,
direction,
verticalDirection,
Directionality.of(context),
);
......@@ -908,11 +1006,13 @@ class _SelectToggleButton extends SingleChildRenderObjectWidget {
void updateRenderObject(BuildContext context, _SelectToggleButtonRenderObject renderObject) {
renderObject
..leadingBorderSide = leadingBorderSide
..horizontalBorderSide = horizontalBorderSide
..borderSide = borderSide
..trailingBorderSide = trailingBorderSide
..borderRadius = borderRadius
..isFirstButton = isFirstButton
..isLastButton = isLastButton
..direction = direction
..verticalDirection = verticalDirection
..textDirection = Directionality.of(context);
}
}
......@@ -920,15 +1020,35 @@ class _SelectToggleButton extends SingleChildRenderObjectWidget {
class _SelectToggleButtonRenderObject extends RenderShiftedBox {
_SelectToggleButtonRenderObject(
this._leadingBorderSide,
this._horizontalBorderSide,
this._borderSide,
this._trailingBorderSide,
this._borderRadius,
this._isFirstButton,
this._isLastButton,
this._direction,
this._verticalDirection,
this._textDirection, [
RenderBox? child,
]) : super(child);
Axis get direction => _direction;
Axis _direction;
set direction(Axis value) {
if (_direction == value)
return;
_direction = value;
markNeedsLayout();
}
VerticalDirection get verticalDirection => _verticalDirection;
VerticalDirection _verticalDirection;
set verticalDirection(VerticalDirection value) {
if (_verticalDirection == value)
return;
_verticalDirection = value;
markNeedsLayout();
}
// The width and color of the button's leading side border.
BorderSide get leadingBorderSide => _leadingBorderSide;
BorderSide _leadingBorderSide;
......@@ -940,12 +1060,12 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
}
// The width and color of the button's top and bottom side borders.
BorderSide get horizontalBorderSide => _horizontalBorderSide;
BorderSide _horizontalBorderSide;
set horizontalBorderSide(BorderSide value) {
if (_horizontalBorderSide == value)
BorderSide get borderSide => _borderSide;
BorderSide _borderSide;
set borderSide(BorderSide value) {
if (_borderSide == value)
return;
_horizontalBorderSide = value;
_borderSide = value;
markNeedsLayout();
}
......@@ -1003,6 +1123,10 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
return box == null ? 0.0 : box.getMaxIntrinsicHeight(width);
}
static double _minHeight(RenderBox? box, double width) {
return box == null ? 0.0 : box.getMinIntrinsicHeight(width);
}
static double _minWidth(RenderBox? box, double height) {
return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
}
......@@ -1014,32 +1138,37 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
// The baseline of this widget is the baseline of its child
return child!.computeDistanceToActualBaseline(baseline)! +
horizontalBorderSide.width;
return direction == Axis.horizontal
? child!.computeDistanceToActualBaseline(baseline)! + borderSide.width
: child!.computeDistanceToActualBaseline(baseline)! + leadingBorderSide.width;
}
@override
double computeMaxIntrinsicHeight(double width) {
return horizontalBorderSide.width +
_maxHeight(child, width) +
horizontalBorderSide.width;
return direction == Axis.horizontal
? borderSide.width * 2.0 + _maxHeight(child, width)
: leadingBorderSide.width + _maxHeight(child, width) + trailingBorderSide.width;
}
@override
double computeMinIntrinsicHeight(double width) => computeMaxIntrinsicHeight(width);
double computeMinIntrinsicHeight(double width) {
return direction == Axis.horizontal
? borderSide.width * 2.0 + _minHeight(child, width)
: leadingBorderSide.width + _maxHeight(child, width) + trailingBorderSide.width;
}
@override
double computeMaxIntrinsicWidth(double height) {
return leadingBorderSide.width +
_maxWidth(child, height) +
trailingBorderSide.width;
return direction == Axis.horizontal
? leadingBorderSide.width + _maxWidth(child, height) + trailingBorderSide.width
: borderSide.width * 2.0 + _maxWidth(child, height);
}
@override
double computeMinIntrinsicWidth(double height) {
return leadingBorderSide.width +
_minWidth(child, height) +
trailingBorderSide.width;
return direction == Axis.horizontal
? leadingBorderSide.width + _minWidth(child, height) + trailingBorderSide.width
: borderSide.width * 2.0 + _minWidth(child, height);
}
@override
......@@ -1059,85 +1188,85 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
if (child == null) {
return;
}
final BoxParentData childParentData = child!.parentData! as BoxParentData;
if (direction == Axis.horizontal) {
switch (textDirection) {
case TextDirection.ltr:
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = Offset(leadingBorderSide.width, leadingBorderSide.width);
childParentData.offset = Offset(leadingBorderSide.width, borderSide.width);
break;
case TextDirection.rtl:
final BoxParentData childParentData = child!.parentData! as BoxParentData;
if (isLastButton) {
childParentData.offset = Offset(_trailingBorderOffset, _trailingBorderOffset);
} else {
childParentData.offset = Offset(0, horizontalBorderSide.width);
childParentData.offset = Offset(trailingBorderSide.width, borderSide.width);
break;
}
} else {
switch (verticalDirection) {
case VerticalDirection.down:
childParentData.offset = Offset(borderSide.width, leadingBorderSide.width);
break;
case VerticalDirection.up:
childParentData.offset = Offset(borderSide.width, trailingBorderSide.width);
break;
}
}
double get _trailingBorderOffset => isLastButton ? trailingBorderSide.width : 0.0;
}
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
if (child == null) {
if (direction == Axis.horizontal) {
return constraints.constrain(Size(
leadingBorderSide.width + trailingBorderSide.width,
borderSide.width * 2.0,
));
} else {
return constraints.constrain(Size(
borderSide.width * 2.0,
leadingBorderSide.width + trailingBorderSide.width,
horizontalBorderSide.width * 2.0,
));
}
}
final double leftConstraint;
final double rightConstraint;
switch (textDirection) {
case TextDirection.ltr:
rightConstraint = _trailingBorderOffset;
final double topConstraint;
final double bottomConstraint;
// It does not matter what [textDirection] or [verticalDirection] is,
// since deflating the size constraints horizontally/vertically
// and the returned size accounts for the width of both sides.
if (direction == Axis.horizontal) {
rightConstraint = trailingBorderSide.width;
leftConstraint = leadingBorderSide.width;
final BoxConstraints innerConstraints = constraints.deflate(
EdgeInsets.only(
left: leftConstraint,
top: horizontalBorderSide.width,
right: rightConstraint,
bottom: horizontalBorderSide.width,
),
);
final Size childSize = layoutChild(child!, innerConstraints);
return constraints.constrain(Size(
leftConstraint + childSize.width + rightConstraint,
horizontalBorderSide.width * 2.0 + childSize.height,
));
case TextDirection.rtl:
rightConstraint = leadingBorderSide.width;
leftConstraint = _trailingBorderOffset;
topConstraint = borderSide.width;
bottomConstraint = borderSide.width;
} else {
rightConstraint = borderSide.width;
leftConstraint = borderSide.width;
topConstraint = leadingBorderSide.width;
bottomConstraint = trailingBorderSide.width;
}
final BoxConstraints innerConstraints = constraints.deflate(
EdgeInsets.only(
left: leftConstraint,
top: horizontalBorderSide.width,
top: topConstraint,
right: rightConstraint,
bottom: horizontalBorderSide.width,
bottom: bottomConstraint,
),
);
final Size childSize = layoutChild(child!, innerConstraints);
return constraints.constrain(Size(
leftConstraint + childSize.width + rightConstraint,
horizontalBorderSide.width * 2.0 + childSize.height,
topConstraint + childSize.height + bottomConstraint,
));
}
}
@override
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
final Offset bottomRight = size.bottomRight(offset);
final Rect outer = Rect.fromLTRB(offset.dx, offset.dy, bottomRight.dx, bottomRight.dy);
final Rect center = outer.deflate(horizontalBorderSide.width / 2.0);
final Rect center = outer.deflate(borderSide.width / 2.0);
const double sweepAngle = math.pi / 2.0;
final RRect rrect = RRect.fromRectAndCorners(
center,
topLeft: borderRadius.topLeft,
......@@ -1145,7 +1274,6 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
bottomLeft: borderRadius.bottomLeft,
bottomRight: borderRadius.bottomRight,
).scaleRadii();
final Rect tlCorner = Rect.fromLTWH(
rrect.left,
rrect.top,
......@@ -1171,27 +1299,28 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
rrect.brRadiusY * 2,
);
if (direction == Axis.horizontal) {
final Paint leadingPaint = leadingBorderSide.toPaint();
switch (textDirection) {
case TextDirection.ltr:
if (isLastButton) {
final Path leftPath = Path()
..moveTo(rrect.left, rrect.bottom + leadingBorderSide.width / 2)
final Path leftPath = Path();
leftPath..moveTo(rrect.left, rrect.bottom + leadingBorderSide.width / 2)
..lineTo(rrect.left, rrect.top - leadingBorderSide.width / 2);
context.canvas.drawPath(leftPath, leadingPaint);
final Paint endingPaint = trailingBorderSide.toPaint();
final Path endingPath = Path()
..moveTo(rrect.left + horizontalBorderSide.width / 2.0, rrect.top)
final Path endingPath = Path();
endingPath..moveTo(rrect.left + borderSide.width / 2.0, rrect.top)
..lineTo(rrect.right - rrect.trRadiusX, rrect.top)
..addArc(trCorner, math.pi * 3.0 / 2.0, sweepAngle)
..lineTo(rrect.right, rrect.bottom - rrect.brRadiusY)
..addArc(brCorner, 0, sweepAngle)
..lineTo(rrect.left + horizontalBorderSide.width / 2.0, rrect.bottom);
..lineTo(rrect.left + borderSide.width / 2.0, rrect.bottom);
context.canvas.drawPath(endingPath, endingPaint);
} else if (isFirstButton) {
final Path leadingPath = Path()
..moveTo(outer.right, rrect.bottom)
final Path leadingPath = Path();
leadingPath..moveTo(outer.right, rrect.bottom)
..lineTo(rrect.left + rrect.blRadiusX, rrect.bottom)
..addArc(blCorner, math.pi / 2.0, sweepAngle)
..lineTo(rrect.left, rrect.top + rrect.tlRadiusY)
......@@ -1199,39 +1328,40 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
..lineTo(outer.right, rrect.top);
context.canvas.drawPath(leadingPath, leadingPaint);
} else {
final Path leadingPath = Path()
..moveTo(rrect.left, rrect.bottom + leadingBorderSide.width / 2)
final Path leadingPath = Path();
leadingPath..moveTo(rrect.left, rrect.bottom + leadingBorderSide.width / 2)
..lineTo(rrect.left, rrect.top - leadingBorderSide.width / 2);
context.canvas.drawPath(leadingPath, leadingPaint);
final Paint horizontalPaint = horizontalBorderSide.toPaint();
final Path horizontalPaths = Path()
..moveTo(rrect.left + horizontalBorderSide.width / 2.0, rrect.top)
final Paint horizontalPaint = borderSide.toPaint();
final Path horizontalPaths = Path();
horizontalPaths..moveTo(rrect.left + borderSide.width / 2.0, rrect.top)
..lineTo(outer.right - rrect.trRadiusX, rrect.top)
..moveTo(rrect.left + horizontalBorderSide.width / 2.0 + rrect.tlRadiusX, rrect.bottom)
..moveTo(rrect.left + borderSide.width / 2.0 + rrect.tlRadiusX, rrect.bottom)
..lineTo(outer.right - rrect.trRadiusX, rrect.bottom);
context.canvas.drawPath(horizontalPaths, horizontalPaint);
}
break;
case TextDirection.rtl:
if (isLastButton) {
final Path leadingPath = Path()
..moveTo(rrect.right, rrect.bottom + leadingBorderSide.width / 2)
final Path leadingPath = Path();
leadingPath..moveTo(rrect.right, rrect.bottom + leadingBorderSide.width / 2)
..lineTo(rrect.right, rrect.top - leadingBorderSide.width / 2);
context.canvas.drawPath(leadingPath, leadingPaint);
final Paint endingPaint = trailingBorderSide.toPaint();
final Path endingPath = Path()
..moveTo(rrect.right - horizontalBorderSide.width / 2.0, rrect.top)
final Path endingPath = Path();
endingPath..moveTo(rrect.right - borderSide.width / 2.0, rrect.top)
..lineTo(rrect.left + rrect.tlRadiusX, rrect.top)
..addArc(tlCorner, math.pi * 3.0 / 2.0, -sweepAngle)
..lineTo(rrect.left, rrect.bottom - rrect.blRadiusY)
..addArc(blCorner, math.pi, -sweepAngle)
..lineTo(rrect.right - horizontalBorderSide.width / 2.0, rrect.bottom);
..lineTo(rrect.right - borderSide.width / 2.0, rrect.bottom);
context.canvas.drawPath(endingPath, endingPaint);
} else if (isFirstButton) {
final Path leadingPath = Path()
..moveTo(outer.left, rrect.bottom)
final Path leadingPath = Path();
leadingPath..moveTo(outer.left, rrect.bottom)
..lineTo(rrect.right - rrect.brRadiusX, rrect.bottom)
..addArc(brCorner, math.pi / 2.0, -sweepAngle)
..lineTo(rrect.right, rrect.top + rrect.trRadiusY)
......@@ -1239,20 +1369,109 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
..lineTo(outer.left, rrect.top);
context.canvas.drawPath(leadingPath, leadingPaint);
} else {
final Path leadingPath = Path()
..moveTo(rrect.right, rrect.bottom + leadingBorderSide.width / 2)
final Path leadingPath = Path();
leadingPath..moveTo(rrect.right, rrect.bottom + leadingBorderSide.width / 2)
..lineTo(rrect.right, rrect.top - leadingBorderSide.width / 2);
context.canvas.drawPath(leadingPath, leadingPaint);
final Paint horizontalPaint = horizontalBorderSide.toPaint();
final Path horizontalPaths = Path()
..moveTo(rrect.right - horizontalBorderSide.width / 2.0, rrect.top)
final Paint horizontalPaint = borderSide.toPaint();
final Path horizontalPaths = Path();
horizontalPaths..moveTo(rrect.right - borderSide.width / 2.0, rrect.top)
..lineTo(outer.left - rrect.tlRadiusX, rrect.top)
..moveTo(rrect.right - horizontalBorderSide.width / 2.0 + rrect.trRadiusX, rrect.bottom)
..moveTo(rrect.right - borderSide.width / 2.0 + rrect.trRadiusX, rrect.bottom)
..lineTo(outer.left - rrect.tlRadiusX, rrect.bottom);
context.canvas.drawPath(horizontalPaths, horizontalPaint);
}
break;
}
} else {
final Paint leadingPaint = leadingBorderSide.toPaint();
switch (verticalDirection) {
case VerticalDirection.down:
if (isLastButton) {
final Path topPath = Path();
topPath..moveTo(outer.left, outer.top + leadingBorderSide.width / 2)
..lineTo(outer.right, outer.top + leadingBorderSide.width / 2);
context.canvas.drawPath(topPath, leadingPaint);
final Paint endingPaint = trailingBorderSide.toPaint();
final Path endingPath = Path();
endingPath..moveTo(rrect.left, rrect.top + leadingBorderSide.width / 2.0)
..lineTo(rrect.left, rrect.bottom - rrect.blRadiusY)
..addArc(blCorner, math.pi * 3.0, -sweepAngle)
..lineTo(rrect.right - rrect.blRadiusX, rrect.bottom)
..addArc(brCorner, math.pi / 2.0, -sweepAngle)
..lineTo(rrect.right, rrect.top + leadingBorderSide.width / 2.0);
context.canvas.drawPath(endingPath, endingPaint);
} else if (isFirstButton) {
final Path leadingPath = Path();
leadingPath..moveTo(rrect.left, outer.bottom)
..lineTo(rrect.left, rrect.top + rrect.tlRadiusX)
..addArc(tlCorner, math.pi, sweepAngle)
..lineTo(rrect.right - rrect.trRadiusX, rrect.top)
..addArc(trCorner, math.pi * 3.0 / 2.0, sweepAngle)
..lineTo(rrect.right, outer.bottom);
context.canvas.drawPath(leadingPath, leadingPaint);
} else {
final Path topPath = Path();
topPath..moveTo(outer.left, outer.top + leadingBorderSide.width / 2)
..lineTo(outer.right, outer.top + leadingBorderSide.width / 2);
context.canvas.drawPath(topPath, leadingPaint);
final Paint paint = borderSide.toPaint();
final Path paths = Path(); // Left and right borders.
paths..moveTo(rrect.left, outer.top + leadingBorderSide.width)
..lineTo(rrect.left, outer.bottom)
..moveTo(rrect.right, outer.top + leadingBorderSide.width)
..lineTo(rrect.right, outer.bottom);
context.canvas.drawPath(paths, paint);
}
break;
case VerticalDirection.up:
if (isLastButton) {
final Path bottomPath = Path();
bottomPath..moveTo(outer.left, outer.bottom - leadingBorderSide.width / 2.0)
..lineTo(outer.right, outer.bottom - leadingBorderSide.width / 2.0);
context.canvas.drawPath(bottomPath, leadingPaint);
final Paint endingPaint = trailingBorderSide.toPaint();
final Path endingPath = Path();
endingPath..moveTo(rrect.left, rrect.bottom - leadingBorderSide.width / 2.0)
..lineTo(rrect.left, rrect.top + rrect.tlRadiusY)
..addArc(tlCorner, math.pi, sweepAngle)
..lineTo(rrect.right - rrect.trRadiusX, rrect.top)
..addArc(trCorner, math.pi * 3.0 / 2.0, sweepAngle)
..lineTo(rrect.right, rrect.bottom - leadingBorderSide.width / 2.0);
context.canvas.drawPath(endingPath, endingPaint);
} else if (isFirstButton) {
final Path leadingPath = Path();
leadingPath..moveTo(rrect.left, outer.top)
..lineTo(rrect.left, rrect.bottom - rrect.blRadiusY)
..addArc(blCorner, math.pi, -sweepAngle)
..lineTo(rrect.right - rrect.brRadiusX, rrect.bottom)
..addArc(brCorner, math.pi / 2.0, -sweepAngle)
..lineTo(rrect.right, outer.top);
context.canvas.drawPath(leadingPath, leadingPaint);
} else {
final Path bottomPath = Path();
bottomPath..moveTo(outer.left, outer.bottom - leadingBorderSide.width / 2.0)
..lineTo(outer.right, outer.bottom - leadingBorderSide.width / 2.0);
context.canvas.drawPath(bottomPath, leadingPaint);
final Paint paint = borderSide.toPaint();
final Path paths = Path(); // Left and right borders.
paths..moveTo(rrect.left, outer.top)
..lineTo(rrect.left, outer.bottom - leadingBorderSide.width)
..moveTo(rrect.right, outer.top)
..lineTo(rrect.right, outer.bottom - leadingBorderSide.width);
context.canvas.drawPath(paths, paint);
}
break;
}
}
}
}
......@@ -1398,6 +1398,156 @@ void main() {
},
);
testWidgets(
'Properly draws borders based on state when direction is vertical and verticalDirection is down.',
(WidgetTester tester) async {
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
direction: Axis.vertical,
verticalDirection: VerticalDirection.down,
isSelected: const <bool>[false, true, false],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
Text('Second child'),
Text('Third child'),
],
),
),
),
);
// The children should be laid out along vertical and the first child at top.
// The item height is icon height + default border width (48.0 + 1.0) pixels.
expect(tester.getCenter(find.text('First child')), const Offset(400.0, 251.0));
expect(tester.getCenter(find.text('Second child')), const Offset(400.0, 300.0));
expect(tester.getCenter(find.text('Third child')), const Offset(400.0, 349.0));
final List<RenderObject> toggleButtonRenderObject = tester.allRenderObjects.where((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
}).toSet().toList();
// The first button paints the left, top and right sides with a path.
expect(
toggleButtonRenderObject[0],
paints
// left side, top and right - enabled.
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
),
);
// The middle buttons paint a top side path first, followed by a
// left and right side path.
expect(
toggleButtonRenderObject[1],
paints
// top side - selected.
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
)
// left and right - selected.
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
),
);
// The last button paints a top side path first, followed by
// a left, bottom and right side path
expect(
toggleButtonRenderObject[2],
paints
// top side - selected, since previous button is selected.
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
)
// left side, bottom and right - enabled.
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
),
);
},
);
testWidgets(
'VerticalDirection test when direction is vertical.',
(WidgetTester tester) async {
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
direction: Axis.vertical,
verticalDirection: VerticalDirection.up,
isSelected: const <bool>[false, true, false],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
Text('Second child'),
Text('Third child'),
],
),
),
),
);
// The children should be laid out along vertical and the last child at top.
expect(tester.getCenter(find.text('Third child')), const Offset(400.0, 251.0));
expect(tester.getCenter(find.text('Second child')), const Offset(400.0, 300.0));
expect(tester.getCenter(find.text('First child')), const Offset(400.0, 349.0));
},
);
testWidgets('ToggleButtons implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
ToggleButtons(
direction: Axis.vertical,
verticalDirection: VerticalDirection.up,
borderWidth: 3.0,
color: Colors.green,
selectedBorderColor: Colors.pink,
disabledColor: Colors.blue,
disabledBorderColor: Colors.yellow,
borderRadius: const BorderRadius.all(Radius.circular(7.0)),
isSelected: const <bool>[false, true, false],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
Text('Second child'),
Text('Third child'),
],
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString()).toList();
expect(description, <String>[
'Buttons are enabled',
'color: MaterialColor(primary value: Color(0xff4caf50))',
'disabledColor: MaterialColor(primary value: Color(0xff2196f3))',
'selectedBorderColor: MaterialColor(primary value: Color(0xffe91e63))',
'disabledBorderColor: MaterialColor(primary value: Color(0xffffeb3b))',
'borderRadius: BorderRadius.circular(7.0)',
'borderWidth: 3.0',
'direction: Axis.vertical',
'verticalDirection: VerticalDirection.up'
]);
});
testWidgets('ToggleButtons changes mouse cursor when the button is hovered', (WidgetTester tester) async {
await tester.pumpWidget(
Material(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment