Unverified Commit 7a8c84f9 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Some misc changes needed for MenuBar implementation. (#109555)

parent bfdc9a6c
......@@ -1320,7 +1320,7 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
}
}
/// Redirects the [position.dy] passed to [RenderBox.hitTest] to the vertical
/// Redirects the [buttonRect.dy] passed to [RenderBox.hitTest] to the vertical
/// center of the widget.
///
/// The primary purpose of this widget is to allow padding around the [RawChip]
......
......@@ -722,7 +722,13 @@ class MaterialStatePropertyAll<T> implements MaterialStateProperty<T> {
T resolve(Set<MaterialState> states) => value;
@override
String toString() => 'MaterialStatePropertyAll($value)';
String toString() {
if (value is double) {
return 'MaterialStatePropertyAll(${debugFormatDouble(value as double)})';
} else {
return 'MaterialStatePropertyAll($value)';
}
}
}
/// Manages a set of [MaterialState]s and notifies listeners of changes.
......
......@@ -232,11 +232,12 @@ class SnackBar extends StatefulWidget {
/// Typically a [Text] widget.
final Widget content;
/// The snack bar's background color. If not specified it will use
/// [SnackBarThemeData.backgroundColor] of [ThemeData.snackBarTheme]. If that
/// is not specified it will default to a dark variation of
/// [ColorScheme.surface] for light themes, or [ColorScheme.onSurface] for
/// dark themes.
/// The snack bar's background color.
///
/// If not specified, it will use [SnackBarThemeData.backgroundColor] of
/// [ThemeData.snackBarTheme]. If that is not specified it will default to a
/// dark variation of [ColorScheme.surface] for light themes, or
/// [ColorScheme.onSurface] for dark themes.
final Color? backgroundColor;
/// The z-coordinate at which to place the snack bar. This controls the size
......
......@@ -674,6 +674,27 @@ class EdgeInsetsDirectional extends EdgeInsetsGeometry {
this.bottom = 0.0,
});
/// Creates insets with symmetric vertical and horizontal offsets.
///
/// This is equivalent to [EdgeInsets.symmetric], since the inset is the same
/// with either [TextDirection]. This constructor is just a convenience for
/// type compatibilty.
///
/// {@tool snippet}
/// Eight pixel margin above and below, no horizontal margins:
///
/// ```dart
/// const EdgeInsetsDirectional.symmetric(vertical: 8.0)
/// ```
/// {@end-tool}
const EdgeInsetsDirectional.symmetric({
double horizontal = 0.0,
double vertical = 0.0,
}) : start = horizontal,
end = horizontal,
top = vertical,
bottom = vertical;
/// Creates insets where all the offsets are `value`.
///
/// {@tool snippet}
......
......@@ -991,10 +991,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
final double remainingSpace = math.max(0.0, actualSizeDelta);
late final double leadingSpace;
late final double betweenSpace;
// flipMainAxis is used to decide whether to lay out left-to-right/top-to-bottom (false), or
// right-to-left/bottom-to-top (true). The _startIsTopLeft will return null if there's only
// one child and the relevant direction is null, in which case we arbitrarily decide not to
// flip, but that doesn't have any detectable effect.
// flipMainAxis is used to decide whether to lay out
// left-to-right/top-to-bottom (false), or right-to-left/bottom-to-top
// (true). The _startIsTopLeft will return null if there's only one child
// and the relevant direction is null, in which case we arbitrarily decide
// to flip, but that doesn't have any detectable effect.
final bool flipMainAxis = !(_startIsTopLeft(direction, textDirection, verticalDirection) ?? true);
switch (_mainAxisAlignment) {
case MainAxisAlignment.start:
......@@ -1104,8 +1105,6 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
);
assert(() {
// Only set this if it's null to save work. It gets reset to null if the
// _direction changes.
final List<DiagnosticsNode> debugOverflowHints = <DiagnosticsNode>[
ErrorDescription(
'The overflowing $runtimeType has an orientation of $_direction.',
......
......@@ -129,17 +129,24 @@ abstract class FocusTraversalPolicy with Diagnosticable {
///
/// The `currentNode` argument must not be null.
///
/// The default implementation returns the [FocusScopeNode.focusedChild], if
/// set, on the nearest scope of the `currentNode`, otherwise, returns the
/// first node from [sortDescendants], or the given `currentNode` if there are
/// no descendants.
/// If `ignoreCurrentFocus` is false or not given, this function returns the
/// [FocusScopeNode.focusedChild], if set, on the nearest scope of the
/// `currentNode`, otherwise, returns the first node from [sortDescendants],
/// or the given `currentNode` if there are no descendants.
///
/// If `ignoreCurrentFocus` is true, then the algorithm returns the first node
/// from [sortDescendants], or the given `currentNode` if there are no
/// descendants.
///
/// See also:
///
/// * [next], the function that is called to move the focus to the next node.
/// * [DirectionalFocusTraversalPolicyMixin.findFirstFocusInDirection], a
/// function that finds the first focusable widget in a particular direction.
FocusNode? findFirstFocus(FocusNode currentNode) => _findInitialFocus(currentNode);
/// * [next], the function that is called to move the focus to the next node.
/// * [DirectionalFocusTraversalPolicyMixin.findFirstFocusInDirection], a
/// function that finds the first focusable widget in a particular
/// direction.
FocusNode? findFirstFocus(FocusNode currentNode, {bool ignoreCurrentFocus = false}) {
return _findInitialFocus(currentNode, ignoreCurrentFocus: ignoreCurrentFocus);
}
/// Returns the node that should receive focus if focus is traversing
/// backwards, and there is no current focus.
......@@ -150,23 +157,29 @@ abstract class FocusTraversalPolicy with Diagnosticable {
///
/// The `currentNode` argument must not be null.
///
/// The default implementation returns the [FocusScopeNode.focusedChild], if
/// set, on the nearest scope of the `currentNode`, otherwise, returns the
/// last node from [sortDescendants], or the given `currentNode` if there are
/// no descendants.
/// If `ignoreCurrentFocus` is false or not given, this function returns the
/// [FocusScopeNode.focusedChild], if set, on the nearest scope of the
/// `currentNode`, otherwise, returns the last node from [sortDescendants],
/// or the given `currentNode` if there are no descendants.
///
/// If `ignoreCurrentFocus` is true, then the algorithm returns the last node
/// from [sortDescendants], or the given `currentNode` if there are no
/// descendants.
///
/// See also:
///
/// * [previous], the function that is called to move the focus to the next node.
/// * [DirectionalFocusTraversalPolicyMixin.findFirstFocusInDirection], a
/// function that finds the first focusable widget in a particular direction.
FocusNode findLastFocus(FocusNode currentNode) => _findInitialFocus(currentNode, fromEnd: true);
FocusNode findLastFocus(FocusNode currentNode, {bool ignoreCurrentFocus = false}) {
return _findInitialFocus(currentNode, fromEnd: true, ignoreCurrentFocus: ignoreCurrentFocus);
}
FocusNode _findInitialFocus(FocusNode currentNode, {bool fromEnd = false}) {
FocusNode _findInitialFocus(FocusNode currentNode, {bool fromEnd = false, bool ignoreCurrentFocus = false}) {
assert(currentNode != null);
final FocusScopeNode scope = currentNode.nearestScope!;
FocusNode? candidate = scope.focusedChild;
if (candidate == null && scope.descendants.isNotEmpty) {
if (ignoreCurrentFocus || candidate == null && scope.descendants.isNotEmpty) {
final Iterable<FocusNode> sorted = _sortAllDescendants(scope, currentNode);
if (sorted.isEmpty) {
candidate = null;
......@@ -200,7 +213,6 @@ abstract class FocusTraversalPolicy with Diagnosticable {
///
/// The default implementation does nothing.
@mustCallSuper
@protected
void invalidateScopeData(FocusScopeNode node) {}
/// This is called whenever the given [node] is re-parented into a new scope,
......@@ -1724,6 +1736,12 @@ class DirectionalFocusIntent extends Intent {
///
/// Defaults to true.
final bool ignoreTextFields;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(EnumProperty<TraversalDirection>('direction', direction));
}
}
/// An [Action] that moves the focus to the focusable node in the direction
......
......@@ -1003,7 +1003,7 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
/// Called whenever the widget configuration changes.
///
/// If the parent widget rebuilds and request that this location in the tree
/// If the parent widget rebuilds and requests that this location in the tree
/// update to display a new widget with the same [runtimeType] and
/// [Widget.key], the framework will update the [widget] property of this
/// [State] object to refer to the new widget and then call this method
......
......@@ -43,6 +43,14 @@ void main() {
expect(value.resolve(<MaterialState>{MaterialState.error}), 123);
});
test('toString formats correctly', () {
const MaterialStateProperty<Color?> colorProperty = MaterialStatePropertyAll<Color?>(Color(0xFFFFFFFF));
expect(colorProperty.toString(), equals('MaterialStatePropertyAll(Color(0xffffffff))'));
const MaterialStateProperty<double?> doubleProperty = MaterialStatePropertyAll<double?>(33 + 1/3);
expect(doubleProperty.toString(), equals('MaterialStatePropertyAll(33.3)'));
});
test("Can interpolate between two MaterialStateProperty's", () {
const MaterialStateProperty<TextStyle?> textStyle1 = MaterialStatePropertyAll<TextStyle?>(
TextStyle(fontSize: 14.0),
......
......@@ -6,6 +6,64 @@ import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('EdgeInsets constructors', () {
// fromLTRB
final EdgeInsets ltrbLtr = const EdgeInsets.fromLTRB(10, 20, 30, 40).resolve(TextDirection.ltr);
expect(ltrbLtr.left, 10);
expect(ltrbLtr.top, 20);
expect(ltrbLtr.right, 30);
expect(ltrbLtr.bottom, 40);
final EdgeInsets ltrbRtl = const EdgeInsets.fromLTRB(10, 20, 30, 40).resolve(TextDirection.rtl);
expect(ltrbRtl.left, 10);
expect(ltrbRtl.top, 20);
expect(ltrbRtl.right, 30);
expect(ltrbRtl.bottom, 40);
// all
const EdgeInsets all = EdgeInsets.all(10);
expect(all.resolve(TextDirection.ltr), equals(const EdgeInsets.fromLTRB(10, 10, 10, 10)));
expect(all.resolve(TextDirection.rtl), equals(const EdgeInsets.fromLTRB(10, 10, 10, 10)));
// only
const EdgeInsets only = EdgeInsets.only(left: 10, top: 20, right: 30, bottom: 40);
expect(only.resolve(TextDirection.ltr), equals(const EdgeInsets.fromLTRB(10, 20, 30, 40)));
expect(only.resolve(TextDirection.rtl), equals(const EdgeInsets.fromLTRB(10, 20, 30, 40)));
// symmetric
const EdgeInsets symmetric = EdgeInsets.symmetric(horizontal: 10, vertical: 20);
expect(symmetric.resolve(TextDirection.ltr), equals(const EdgeInsets.fromLTRB(10, 20, 10, 20)));
expect(symmetric.resolve(TextDirection.rtl), equals(const EdgeInsets.fromLTRB(10, 20, 10, 20)));
});
test('EdgeInsetsDirectional constructors', () {
// fromSTEB
final EdgeInsets stebLtr = const EdgeInsetsDirectional.fromSTEB(10, 20, 30, 40).resolve(TextDirection.ltr);
expect(stebLtr.left, 10);
expect(stebLtr.top, 20);
expect(stebLtr.right, 30);
expect(stebLtr.bottom, 40);
final EdgeInsets stebRtl = const EdgeInsetsDirectional.fromSTEB(10, 20, 30, 40).resolve(TextDirection.rtl);
expect(stebRtl.left, 30);
expect(stebRtl.top, 20);
expect(stebRtl.right, 10);
expect(stebRtl.bottom, 40);
// all
const EdgeInsetsDirectional all = EdgeInsetsDirectional.all(10);
expect(all.resolve(TextDirection.ltr), equals(const EdgeInsets.fromLTRB(10, 10, 10, 10)));
expect(all.resolve(TextDirection.rtl), equals(const EdgeInsets.fromLTRB(10, 10, 10, 10)));
// only
const EdgeInsetsDirectional directional = EdgeInsetsDirectional.only(start: 10, top: 20, end: 30, bottom: 40);
expect(directional.resolve(TextDirection.ltr), const EdgeInsets.fromLTRB(10, 20, 30, 40));
expect(directional.resolve(TextDirection.rtl), const EdgeInsets.fromLTRB(30, 20, 10, 40));
// symmetric
const EdgeInsetsDirectional symmetric = EdgeInsetsDirectional.symmetric(horizontal: 10, vertical: 20);
expect(symmetric.resolve(TextDirection.ltr), equals(const EdgeInsets.fromLTRB(10, 20, 10, 20)));
expect(symmetric.resolve(TextDirection.rtl), equals(const EdgeInsets.fromLTRB(10, 20, 10, 20)));
});
test('EdgeInsets control test', () {
const EdgeInsets insets = EdgeInsets.fromLTRB(5.0, 7.0, 11.0, 13.0);
......
......@@ -910,7 +910,7 @@ void main() {
expect(
description[0],
equalsIgnoringHashCodes(
'shortcuts: {{Shift + Key A}: ActivateIntent#00000, {Shift + Arrow Right}: DirectionalFocusIntent#00000}',
'shortcuts: {{Shift + Key A}: ActivateIntent#00000, {Shift + Arrow Right}: DirectionalFocusIntent#00000(direction: right)}',
),
);
});
......
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