Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Correct Badge interpretation of its alignment parameter (#119853)
Browse files Browse the repository at this point in the history
  • Loading branch information
HansMuller committed Feb 8, 2023
1 parent d8154fd commit 75ca31b
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 50 deletions.
2 changes: 1 addition & 1 deletion dev/tools/gen_defaults/lib/badge_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class _${blockName}DefaultsM3 extends BadgeThemeData {
smallSize: ${tokens["md.comp.badge.size"]},
largeSize: ${tokens["md.comp.badge.large.size"]},
padding: const EdgeInsets.symmetric(horizontal: 4),
alignment: const AlignmentDirectional(12, -4),
alignment: AlignmentDirectional.topEnd,
);
final BuildContext context;
Expand Down
118 changes: 106 additions & 12 deletions packages/flutter/lib/src/material/badge.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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/rendering.dart';
import 'package:flutter/widgets.dart';

import 'badge_theme.dart';
Expand Down Expand Up @@ -35,6 +36,7 @@ class Badge extends StatelessWidget {
this.textStyle,
this.padding,
this.alignment,
this.offset,
this.label,
this.isLabelVisible = true,
this.child,
Expand All @@ -54,6 +56,7 @@ class Badge extends StatelessWidget {
this.textStyle,
this.padding,
this.alignment,
this.offset,
required int count,
this.isLabelVisible = true,
this.child,
Expand Down Expand Up @@ -106,13 +109,29 @@ class Badge extends StatelessWidget {
/// left and right if the theme's value is null.
final EdgeInsetsGeometry? padding;

/// The location of the [label] relative to the [child].
/// Combined with [offset] to determine the location of the [label]
/// relative to the [child].
///
/// The alignment positions the label in the same way a child of an
/// [Align] widget is positioned, except that, the alignment is
/// resolved as if the label was a [largeSize] square and [offset]
/// is added to the result.
///
/// This value is only used if [label] is non-null.
///
/// Defaults to the [BadgeTheme]'s alignment, or
/// [AlignmentDirectional.topEnd] if the theme's value is null.
final AlignmentGeometry? alignment;

/// Combined with [alignment] to determine the location of the [label]
/// relative to the [child].
///
/// This value is only used if [label] is non-null.
///
/// Defaults to the [BadgeTheme]'s alignment, or `start = 12`
/// and `top = -4` if the theme's value is null.
final AlignmentDirectional? alignment;
/// Defaults to the [BadgeTheme]'s offset, or
/// if the theme's value is null then `Offset(4, -4)` for
/// [TextDirection.ltr] or `Offset(-4, -4)` for [TextDirection.rtl].
final Offset? offset;

/// The badge's content, typically a [Text] widget that contains 1 to 4
/// characters.
Expand Down Expand Up @@ -168,24 +187,99 @@ class Badge extends StatelessWidget {
return badge;
}

final AlignmentDirectional effectiveAlignment = alignment ?? badgeTheme.alignment ?? defaults.alignment!;
final AlignmentGeometry effectiveAlignment = alignment ?? badgeTheme.alignment ?? defaults.alignment!;
final TextDirection textDirection = Directionality.of(context);
final Offset defaultOffset = textDirection == TextDirection.ltr ? const Offset(4, -4) : const Offset(-4, -4);
final Offset effectiveOffset = offset ?? badgeTheme.offset ?? defaultOffset;

return
Stack(
clipBehavior: Clip.none,
children: <Widget>[
child!,
Positioned.directional(
textDirection: Directionality.of(context),
start: label == null ? null : effectiveAlignment.start,
end: label == null ? 0 : null,
top: label == null ? 0 : effectiveAlignment.y,
child: badge,
Positioned.fill(
child: _Badge(
alignment: effectiveAlignment,
offset: label == null ? Offset.zero : effectiveOffset,
textDirection: textDirection,
child: badge,
),
),
],
);
}
}

class _Badge extends SingleChildRenderObjectWidget {
const _Badge({
required this.alignment,
required this.offset,
required this.textDirection,
super.child, // the badge
});

final AlignmentGeometry alignment;
final Offset offset;
final TextDirection textDirection;

@override
_RenderBadge createRenderObject(BuildContext context) {
return _RenderBadge(
alignment: alignment,
offset: offset,
textDirection: Directionality.maybeOf(context),
);
}

@override
void updateRenderObject(BuildContext context, _RenderBadge renderObject) {
renderObject
..alignment = alignment
..offset = offset
..textDirection = Directionality.maybeOf(context);
}

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
properties.add(DiagnosticsProperty<Offset>('offset', offset));
}
}

class _RenderBadge extends RenderAligningShiftedBox {
_RenderBadge({
super.textDirection,
super.alignment,
required Offset offset,
}) : _offset = offset;

Offset get offset => _offset;
Offset _offset;
set offset(Offset value) {
if (_offset == value) {
return;
}
_offset = value;
markNeedsLayout();
}

@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
assert(constraints.hasBoundedWidth);
assert(constraints.hasBoundedHeight);
size = constraints.biggest;

child!.layout(const BoxConstraints(), parentUsesSize: true);
final double badgeSize = child!.size.height;
final Alignment resolvedAlignment = alignment.resolve(textDirection);
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = offset + resolvedAlignment.alongOffset(Offset(size.width - badgeSize, size.height - badgeSize));
}
}


// BEGIN GENERATED TOKEN PROPERTIES - Badge

// Do not edit by hand. The code between the "BEGIN GENERATED" and
Expand All @@ -200,7 +294,7 @@ class _BadgeDefaultsM3 extends BadgeThemeData {
smallSize: 6.0,
largeSize: 16.0,
padding: const EdgeInsets.symmetric(horizontal: 4),
alignment: const AlignmentDirectional(12, -4),
alignment: AlignmentDirectional.topEnd,
);

final BuildContext context;
Expand Down
20 changes: 15 additions & 5 deletions packages/flutter/lib/src/material/badge_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class BadgeThemeData with Diagnosticable {
this.textStyle,
this.padding,
this.alignment,
this.offset,
});

/// Overrides the default value for [Badge.backgroundColor].
Expand All @@ -62,7 +63,10 @@ class BadgeThemeData with Diagnosticable {
final EdgeInsetsGeometry? padding;

/// Overrides the default value for [Badge.alignment].
final AlignmentDirectional? alignment;
final AlignmentGeometry? alignment;

/// Overrides the default value for [Badge.offset].
final Offset? offset;

/// Creates a copy of this object but with the given fields replaced with the
/// new values.
Expand All @@ -73,7 +77,8 @@ class BadgeThemeData with Diagnosticable {
double? largeSize,
TextStyle? textStyle,
EdgeInsetsGeometry? padding,
AlignmentDirectional? alignment,
AlignmentGeometry? alignment,
Offset? offset,
}) {
return BadgeThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
Expand All @@ -83,6 +88,7 @@ class BadgeThemeData with Diagnosticable {
textStyle: textStyle ?? this.textStyle,
padding: padding ?? this.padding,
alignment: alignment ?? this.alignment,
offset: offset ?? this.offset,
);
}

Expand All @@ -95,7 +101,8 @@ class BadgeThemeData with Diagnosticable {
largeSize: lerpDouble(a?.largeSize, b?.largeSize, t),
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
alignment: AlignmentDirectional.lerp(a?.alignment, b?.alignment, t),
alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
offset: Offset.lerp(a?.offset, b?.offset, t),
);
}

Expand All @@ -108,6 +115,7 @@ class BadgeThemeData with Diagnosticable {
textStyle,
padding,
alignment,
offset,
);

@override
Expand All @@ -125,7 +133,8 @@ class BadgeThemeData with Diagnosticable {
&& other.largeSize == largeSize
&& other.textStyle == textStyle
&& other.padding == padding
&& other.alignment == alignment;
&& other.alignment == alignment
&& other.offset == offset;
}

@override
Expand All @@ -137,7 +146,8 @@ class BadgeThemeData with Diagnosticable {
properties.add(DoubleProperty('largeSize', largeSize, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<AlignmentDirectional>('alignment', alignment, defaultValue: null));
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
properties.add(DiagnosticsProperty<Offset>('offset', offset, defaultValue: null));
}
}

Expand Down
Loading

0 comments on commit 75ca31b

Please sign in to comment.