Minor fix compendium (#107874)

This commit is contained in:
Ian Hickson 2022-08-10 19:33:07 -07:00 committed by GitHub
parent da88c953c7
commit 9b2668a451
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 524 additions and 56 deletions

View file

@ -4,4 +4,4 @@ The [snippets] tool uses the files in the `skeletons` directory to inject code
blocks generated from `{@tool dartpad}`, `{@tool sample}`, and `{@tool snippet}`
sections found in doc comments into the API docs.
[snippet]: https://github.com/flutter/assets-for-api-docs/tree/master/packages/snippets
[snippets]: https://github.com/flutter/assets-for-api-docs/tree/master/packages/snippets

View file

@ -30,6 +30,10 @@ that on an Android device like so:
## Naming
> `lib/library/file/class_name.n.dart`
>
> `lib/library/file/class_name.member_name.n.dart`
The naming scheme for the files is similar to the hierarchy under
[packages/flutter/lib/src](../../packages/flutter/lib/src), except that the
files are represented as directories (without the `.dart` suffix), and each

View file

@ -0,0 +1,26 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for WidgetsApp
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return WidgetsApp(
title: 'Example',
color: const Color(0xFF000000),
home: const Center(child: Text('Hello World')),
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => PageRouteBuilder<T>(
settings: settings,
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => builder(context),
),
);
}
}

View file

@ -0,0 +1,16 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_api_samples/widgets/app/widgets_app.widgets_app.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('WidgetsApp test', (WidgetTester tester) async {
await tester.pumpWidget(
const example.MyApp(),
);
expect(find.text('Hello World'), findsOneWidget);
});
}

View file

@ -19,6 +19,10 @@ import 'localizations.dart';
/// assert(debugCheckHasCupertinoLocalizations(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// Does nothing if asserts are disabled. Always returns true.
bool debugCheckHasCupertinoLocalizations(BuildContext context) {
assert(() {

View file

@ -4,7 +4,6 @@
import 'package:flutter/foundation.dart' show ValueListenable, clampDouble;
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'button.dart';
@ -32,8 +31,11 @@ const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.wit
darkColor: Color(0xff302928),
);
class _CupertinoDesktopTextSelectionControls extends TextSelectionControls {
/// Desktop Cupertino styled text selection controls.
///
/// The [cupertinoDesktopTextSelectionControls] global variable has a
/// suitable instance of this class.
class CupertinoDesktopTextSelectionControls extends TextSelectionControls {
/// Desktop has no text selection handles.
@override
Size getHandleSize(double textLineHeight) {
@ -87,7 +89,7 @@ class _CupertinoDesktopTextSelectionControls extends TextSelectionControls {
/// Text selection controls that follows Mac design conventions.
final TextSelectionControls cupertinoDesktopTextSelectionControls =
_CupertinoDesktopTextSelectionControls();
CupertinoDesktopTextSelectionControls();
// Generates the child that's passed into CupertinoDesktopTextSelectionToolbar.
class _CupertinoDesktopTextSelectionControlsToolbar extends StatefulWidget {

View file

@ -5,7 +5,6 @@
import 'dart:math' as math;
import 'package:flutter/foundation.dart' show ValueListenable, clampDouble;
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'localizations.dart';
@ -192,6 +191,9 @@ class _TextSelectionHandlePainter extends CustomPainter {
}
/// iOS Cupertino styled text selection controls.
///
/// The [cupertinoTextSelectionControls] global variable has a
/// suitable instance of this class.
class CupertinoTextSelectionControls extends TextSelectionControls {
/// Returns the size of the Cupertino handle.
@override

View file

@ -23,6 +23,10 @@ import 'scaffold.dart' show Scaffold, ScaffoldMessenger;
/// assert(debugCheckHasMaterial(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// This method can be expensive (it walks the element tree).
///
/// Does nothing if asserts are disabled. Always returns true.
@ -67,6 +71,10 @@ bool debugCheckHasMaterial(BuildContext context) {
/// assert(debugCheckHasMaterialLocalizations(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// This function has the side-effect of establishing an inheritance
/// relationship with the nearest [Localizations] widget (see
/// [BuildContext.dependOnInheritedWidgetOfExactType]). This is ok if the caller
@ -112,6 +120,10 @@ bool debugCheckHasMaterialLocalizations(BuildContext context) {
/// assert(debugCheckHasScaffold(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// This method can be expensive (it walks the element tree).
///
/// Does nothing if asserts are disabled. Always returns true.
@ -145,6 +157,10 @@ bool debugCheckHasScaffold(BuildContext context) {
/// assert(debugCheckHasScaffoldMessenger(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// This method can be expensive (it walks the element tree).
///
/// Does nothing if asserts are disabled. Always returns true.

View file

@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'package:flutter/foundation.dart' show ValueListenable, clampDouble;
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
@ -19,7 +18,11 @@ import 'theme.dart';
const double _kToolbarScreenPadding = 8.0;
const double _kToolbarWidth = 222.0;
class _DesktopTextSelectionControls extends TextSelectionControls {
/// Desktop Material styled text selection controls.
///
/// The [desktopTextSelectionControls] global variable has a
/// suitable instance of this class.
class DesktopTextSelectionControls extends TextSelectionControls {
/// Desktop has no text selection handles.
@override
Size getHandleSize(double textLineHeight) {
@ -83,7 +86,7 @@ class _DesktopTextSelectionControls extends TextSelectionControls {
/// Text selection controls that loosely follows Material design conventions.
final TextSelectionControls desktopTextSelectionControls =
_DesktopTextSelectionControls();
DesktopTextSelectionControls();
// Generates the child that's passed into DesktopTextSelectionToolbar.
class _DesktopTextSelectionControlsToolbar extends StatefulWidget {
@ -145,12 +148,14 @@ class _DesktopTextSelectionControlsToolbarState extends State<_DesktopTextSelect
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
assert(debugCheckHasMediaQuery(context));
// Don't render the menu until the state of the clipboard is known.
if (widget.handlePaste != null && widget.clipboardStatus?.value == ClipboardStatus.unknown) {
return const SizedBox(width: 0.0, height: 0.0);
}
assert(debugCheckHasMediaQuery(context));
final MediaQueryData mediaQuery = MediaQuery.of(context);
final Offset midpointAnchor = Offset(
@ -161,7 +166,6 @@ class _DesktopTextSelectionControlsToolbarState extends State<_DesktopTextSelect
widget.selectionMidpoint.dy - widget.globalEditableRegion.top,
);
assert(debugCheckHasMaterialLocalizations(context));
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final List<Widget> items = <Widget>[];

View file

@ -4,6 +4,7 @@
import 'package:flutter/cupertino.dart';
import 'debug.dart';
import 'desktop_text_selection.dart';
import 'magnifier.dart';
import 'text_selection.dart';
@ -19,6 +20,10 @@ import 'theme.dart';
/// a specific screen, consider wrapping the body of the [Route] with a
/// [SelectionArea].
///
/// The [SelectionArea] widget must have a [Localizations] ancestor that
/// contains a [MaterialLocalizations] delegate; using the [MaterialApp] widget
/// ensures that such an ancestor is present.
///
/// {@tool dartpad}
/// This example shows how to make a screen selectable.
///
@ -85,6 +90,7 @@ class _SelectionAreaState extends State<SelectionArea> {
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
TextSelectionControls? controls = widget.selectionControls;
switch (Theme.of(context).platform) {
case TargetPlatform.android:

View file

@ -5,7 +5,6 @@
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'debug.dart';
@ -22,6 +21,9 @@ const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0;
const double _kToolbarContentDistance = 8.0;
/// Android Material styled text selection controls.
///
/// The [materialTextSelectionControls] global variable has a
/// suitable instance of this class.
class MaterialTextSelectionControls extends TextSelectionControls {
/// Returns the size of the Material handle.
@override

View file

@ -420,6 +420,8 @@ class RenderParagraph extends RenderBox
}
/// The color to use when painting the selection.
///
/// Ignored if the text is not selectable (e.g. if [registrar] is null).
Color? get selectionColor => _selectionColor;
Color? _selectionColor;
set selectionColor(Color? value) {

View file

@ -282,7 +282,7 @@ class WidgetsApp extends StatefulWidget {
///
/// If [home] or [routes] are not null, the routing implementation needs to know how
/// appropriately build [PageRoute]s. This can be achieved by supplying the
/// [pageRouteBuilder] parameter. The [pageRouteBuilder] is used by [MaterialApp]
/// [pageRouteBuilder] parameter. The [pageRouteBuilder] is used by [MaterialApp]
/// and [CupertinoApp] to create [MaterialPageRoute]s and [CupertinoPageRoute],
/// respectively.
///
@ -296,15 +296,21 @@ class WidgetsApp extends StatefulWidget {
/// and [builder] are null, or if they fail to create a requested route,
/// [onGenerateRoute] will be invoked. If that fails, [onUnknownRoute] will be invoked.
///
/// The [pageRouteBuilder] will create a [PageRoute] that wraps newly built routes.
/// The [pageRouteBuilder] is called to create a [PageRoute] that wraps newly built routes.
/// If the [builder] is non-null and the [onGenerateRoute] argument is null, then the
/// [builder] will not be provided only with the context and the child widget, whereas
/// the [pageRouteBuilder] will be provided with [RouteSettings]. If [onGenerateRoute]
/// is not provided, [navigatorKey], [onUnknownRoute], [navigatorObservers], and
/// [initialRoute] must have their default values, as they will have no effect.
/// [builder] will be provided only with the context and the child widget, whereas
/// the [pageRouteBuilder] will be provided with [RouteSettings]; in that configuration,
/// the [navigatorKey], [onUnknownRoute], [navigatorObservers], and
/// [initialRoute] properties must have their default values, as they will have no effect.
///
/// The `supportedLocales` argument must be a list of one or more elements.
/// By default supportedLocales is `[const Locale('en', 'US')]`.
///
/// {@tool dartpad}
/// This sample shows a basic Flutter application using [WidgetsApp].
///
/// ** See code in examples/api/lib/widgets/app/widgets_app.widgets_app.0.dart **
/// {@end-tool}
WidgetsApp({ // can't be const because the asserts use methods on Iterable :-(
super.key,
this.navigatorKey,
@ -530,8 +536,23 @@ class WidgetsApp extends StatefulWidget {
/// The [PageRoute] generator callback used when the app is navigated to a
/// named route.
///
/// A [PageRoute] represents the page in a [Navigator], so that it can
/// correctly animate between pages, and to represent the "return value" of
/// a route (e.g. which button a user selected in a modal dialog).
///
/// This callback can be used, for example, to specify that a [MaterialPageRoute]
/// or a [CupertinoPageRoute] should be used for building page transitions.
///
/// The [PageRouteFactory] type is generic, meaning the provided function must
/// itself be generic. For example (with special emphasis on the `<T>` at the
/// start of the closure):
///
/// ```dart
/// pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => PageRouteBuilder<T>(
/// settings: settings,
/// pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => builder(context),
/// ),
/// ```
final PageRouteFactory? pageRouteBuilder;
/// {@template flutter.widgets.widgetsApp.routeInformationParser}

View file

@ -5657,9 +5657,17 @@ class RichText extends MultiChildRenderObjectWidget {
final ui.TextHeightBehavior? textHeightBehavior;
/// The [SelectionRegistrar] this rich text is subscribed to.
///
/// If this is set, [selectionColor] must be non-null.
final SelectionRegistrar? selectionRegistrar;
/// The color to use when painting the selection.
///
/// This is ignored if [selectionRegistrar] is null.
///
/// See the section on selections in the [RichText] top-level API
/// documentation for more details on enabling selection in [RichText]
/// widgets.
final Color? selectionColor;
@override

View file

@ -252,6 +252,10 @@ bool debugItemsHaveDuplicateKeys(Iterable<Widget> items) {
/// assert(debugCheckHasTable(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// This method can be expensive (it walks the element tree).
///
/// Does nothing if asserts are disabled. Always returns true.
@ -282,6 +286,10 @@ bool debugCheckHasTable(BuildContext context) {
/// assert(debugCheckHasMediaQuery(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// Does nothing if asserts are disabled. Always returns true.
bool debugCheckHasMediaQuery(BuildContext context) {
assert(() {
@ -334,6 +342,10 @@ bool debugCheckHasMediaQuery(BuildContext context) {
/// If they are non-null, they are included in the order above, interspersed
/// with the more generic advice regarding [Directionality].
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// Does nothing if asserts are disabled. Always returns true.
bool debugCheckHasDirectionality(BuildContext context, { String? why, String? hint, String? alternative }) {
assert(() {
@ -406,6 +418,10 @@ void debugWidgetBuilderValue(Widget widget, Widget? built) {
/// assert(debugCheckHasWidgetsLocalizations(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// Does nothing if asserts are disabled. Always returns true.
bool debugCheckHasWidgetsLocalizations(BuildContext context) {
assert(() {
@ -443,6 +459,10 @@ bool debugCheckHasWidgetsLocalizations(BuildContext context) {
/// assert(debugCheckHasOverlay(context));
/// ```
///
/// Always place this before any early returns, so that the invariant is checked
/// in all cases. This prevents bugs from hiding until a particular codepath is
/// hit.
///
/// This method can be expensive (it walks the element tree).
///
/// Does nothing if asserts are disabled. Always returns true.

View file

@ -31,7 +31,7 @@ class DefaultSelectionStyle extends InheritedTheme {
});
/// A const-constructable default selection style that provides fallback
/// values.
/// values (null).
///
/// Returned from [of] when the given [BuildContext] doesn't have an enclosing
/// default selection style.
@ -43,6 +43,12 @@ class DefaultSelectionStyle extends InheritedTheme {
selectionColor = null,
super(child: const _NullWidget());
/// The default cursor and selection color (semi-transparent grey).
///
/// This is the color that the [Text] widget uses when the specified selection
/// color is null.
static const Color defaultColor = Color(0x80808080);
/// The color of the text field's cursor.
///
/// The cursor indicates the current location of the text insertion point in
@ -88,9 +94,9 @@ class _NullWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
throw FlutterError(
'A DefaultTextStyle constructed with DefaultTextStyle.fallback cannot be incorporated into the widget tree, '
'it is meant only to provide a fallback value returned by DefaultTextStyle.of() '
'when no enclosing default text style is present in a BuildContext.',
'A DefaultSelectionStyle constructed with DefaultSelectionStyle.fallback cannot be incorporated into the widget tree, '
'it is meant only to provide a fallback value returned by DefaultSelectionStyle.of() '
'when no enclosing default selection style is present in a BuildContext.',
);
}
}

View file

@ -572,7 +572,7 @@ class EditableText extends StatefulWidget {
///
/// The [controller], [focusNode], [obscureText], [autocorrect], [autofocus],
/// [showSelectionHandles], [enableInteractiveSelection], [forceLine],
/// [style], [cursorColor], [cursorOpacityAnimates],[backgroundCursorColor],
/// [style], [cursorColor], [cursorOpacityAnimates], [backgroundCursorColor],
/// [enableSuggestions], [paintCursorAboveText], [selectionHeightStyle],
/// [selectionWidthStyle], [textAlign], [dragStartBehavior], [scrollPadding],
/// [dragStartBehavior], [toolbarOptions], [rendererIgnoresPointer],

View file

@ -281,6 +281,10 @@ class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
/// navigation and being able to insert widgets on top of the pages in an app.
/// To simply display a stack of widgets, consider using [Stack] instead.
///
/// An [Overlay] widget requires a [Directionality] widget to be in scope, so
/// that it can resolve direction-sensitive coordinates of any
/// [Positioned.directional] children.
///
/// {@tool dartpad}
/// This example shows how to use the [Overlay] to highlight the [NavigationBar]
/// destination.

View file

@ -7,6 +7,13 @@ import 'framework.dart';
import 'routes.dart';
/// A modal route that replaces the entire screen.
///
/// The [PageRouteBuilder] subclass provides a way to create a [PageRoute] using
/// callbacks rather than by defining a new class via subclassing.
///
/// See also:
///
/// * [Route], which documents the meaning of the `T` generic type argument.
abstract class PageRoute<T> extends ModalRoute<T> {
/// Creates a modal route that replaces the entire screen.
PageRoute({
@ -49,6 +56,13 @@ Widget _defaultTransitionsBuilder(BuildContext context, Animation<double> animat
///
/// Callers must define the [pageBuilder] function which creates the route's
/// primary contents. To add transitions define the [transitionsBuilder] function.
///
/// The `T` generic type argument corresponds to the type argument of the
/// created [Route] objects.
///
/// See also:
///
/// * [Route], which documents the meaning of the `T` generic type argument.
class PageRouteBuilder<T> extends PageRoute<T> {
/// Creates a route that delegates to builder callbacks.
///
@ -86,6 +100,8 @@ class PageRouteBuilder<T> extends PageRoute<T> {
///
/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
/// {@endtemplate}
///
/// The default transition is a jump cut (i.e. no animation).
final RouteTransitionsBuilder transitionsBuilder;
@override

View file

@ -34,6 +34,10 @@ import 'transitions.dart';
// late StateSetter setState;
/// A route that displays widgets in the [Navigator]'s [Overlay].
///
/// See also:
///
/// * [Route], which documents the meaning of the `T` generic type argument.
abstract class OverlayRoute<T> extends Route<T> {
/// Creates a route that knows how to interact with an [Overlay].
OverlayRoute({
@ -85,6 +89,10 @@ abstract class OverlayRoute<T> extends Route<T> {
}
/// A route with entrance and exit transitions.
///
/// See also:
///
/// * [Route], which documents the meaning of the `T` generic type argument.
abstract class TransitionRoute<T> extends OverlayRoute<T> {
/// Creates a route that animates itself when it is pushed or popped.
TransitionRoute({
@ -507,6 +515,10 @@ class LocalHistoryEntry {
/// pop internally if its list of local history entries is non-empty. Rather
/// than being removed as the current route, the most recent [LocalHistoryEntry]
/// is removed from the list and its [LocalHistoryEntry.onRemove] is called.
///
/// See also:
///
/// * [Route], which documents the meaning of the `T` generic type argument.
mixin LocalHistoryRoute<T> on Route<T> {
List<LocalHistoryEntry>? _localHistory;
int _entriesImpliesAppBarDismissal = 0;
@ -946,6 +958,10 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
///
/// The `T` type argument is the return value of the route. If there is no
/// return value, consider using `void` as the return value.
///
/// See also:
///
/// * [Route], which further documents the meaning of the `T` generic type argument.
abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
/// Creates a route that blocks interaction with previous routes.
ModalRoute({

View file

@ -26,6 +26,10 @@ import 'selection_container.dart';
import 'text_editing_intents.dart';
import 'text_selection.dart';
// Examples can assume:
// FocusNode _focusNode = FocusNode();
// late GlobalKey key;
const Set<PointerDeviceKind> _kLongPressSelectionDevices = <PointerDeviceKind>{
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
@ -34,13 +38,22 @@ const Set<PointerDeviceKind> _kLongPressSelectionDevices = <PointerDeviceKind>{
/// A widget that introduces an area for user selections.
///
/// Flutter widgets are not selectable by default. To enable selection for
/// a Flutter application, consider wrapping a portion of widget subtree with
/// [SelectableRegion]. The wrapped subtree can be selected by users using mouse
/// or touch gestures, e.g. users can select widgets by holding the mouse
/// Flutter widgets are not selectable by default. Wrapping a widget subtree
/// with a [SelectableRegion] widget enables selection within that subtree (for
/// example, [Text] widgets automatically look for selectable regions to enable
/// selection). The wrapped subtree can be selected by users using mouse or
/// touch gestures, e.g. users can select widgets by holding the mouse
/// left-click and dragging across widgets, or they can use long press gestures
/// to select words on touch devices.
///
/// A [SelectableRegion] widget requires configuration; in particular specific
/// [selectionControls] must be provided.
///
/// The [SelectionArea] widget from the [material] library configures a
/// [SelectableRegion] in a platform-specific manner (e.g. using a Material
/// toolbar on Android, a Cupertino toolbar on iOS), and it may therefore be
/// simpler to use that widget rather than using [SelectableRegion] directly.
///
/// ## An overview of the selection system.
///
/// Every [Selectable] under the [SelectableRegion] can be selected. They form a
@ -77,7 +90,7 @@ const Set<PointerDeviceKind> _kLongPressSelectionDevices = <PointerDeviceKind>{
/// MaterialApp(
/// home: SelectableRegion(
/// selectionControls: materialTextSelectionControls,
/// focusNode: FocusNode(),
/// focusNode: _focusNode, // initialized to FocusNode()
/// child: Scaffold(
/// appBar: AppBar(title: const Text('Flutter Code Sample')),
/// body: ListView(
@ -160,6 +173,16 @@ const Set<PointerDeviceKind> _kLongPressSelectionDevices = <PointerDeviceKind>{
/// child selection area can not extend past its subtree, and the selection of
/// the parent selection area can not extend inside the child selection area.
///
/// ## Tests
///
/// In a test, a region can be selected either by faking drag events (e.g. using
/// [WidgetTester.dragFrom]) or by sending intents to a widget inside the region
/// that has been given a [GlobalKey], e.g.:
///
/// ```dart
/// Actions.invoke(key.currentContext!, const SelectAllTextIntent(SelectionChangedCause.keyboard));
/// ```
///
/// See also:
/// * [SelectionArea], which creates a [SelectableRegion] with
/// platform-adaptive selection controls.
@ -202,6 +225,9 @@ class SelectableRegion extends StatefulWidget {
/// The delegate to build the selection handles and toolbar for mobile
/// devices.
///
/// The [emptyTextSelectionControls] global variable provides a default
/// [TextSelectionControls] implementation with no controls.
final TextSelectionControls selectionControls;
@override

View file

@ -14,7 +14,8 @@ import 'framework.dart';
///
/// The state of this container is a single selectable and will register
/// itself to the [registrar] if provided. Otherwise, it will register to the
/// [SelectionRegistrar] from the context.
/// [SelectionRegistrar] from the context. Consider using a [SelectionArea]
/// widget to provide a root registrar.
///
/// The containers handle the [SelectionEvent]s from the registered
/// [SelectionRegistrar] and delegate the events to the [delegate].

View file

@ -533,6 +533,13 @@ class Text extends StatelessWidget {
final ui.TextHeightBehavior? textHeightBehavior;
/// The color to use when painting the selection.
///
/// This is ignored if [SelectionContainer.maybeOf] returns null
/// in the [BuildContext] of the [Text] widget.
///
/// If null, the ambient [DefaultSelectionStyle] is used (if any); failing
/// that, the selection color defaults to [DefaultSelectionStyle.defaultColor]
/// (semi-transparent grey).
final Color? selectionColor;
@override
@ -558,7 +565,7 @@ class Text extends StatelessWidget {
textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
selectionRegistrar: registrar,
selectionColor: selectionColor ?? DefaultSelectionStyle.of(context).selectionColor,
selectionColor: selectionColor ?? DefaultSelectionStyle.of(context).selectionColor ?? DefaultSelectionStyle.defaultColor,
text: TextSpan(
style: effectiveTextStyle,
text: data,

View file

@ -26,6 +26,7 @@ import 'tap_region.dart';
import 'ticker_provider.dart';
import 'transitions.dart';
export 'package:flutter/rendering.dart' show TextSelectionPoint;
export 'package:flutter/services.dart' show TextSelectionDelegate;
/// A duration that controls how often the drag selection update callback is
@ -76,6 +77,11 @@ class ToolbarItemsParentData extends ContainerBoxParentData<RenderBox> {
/// implementer of the toolbar widget.
///
/// Override text operations such as [handleCut] if needed.
///
/// See also:
///
/// * [SelectionArea], which selects appropriate text selection controls
/// based on the current platform.
abstract class TextSelectionControls {
/// Builds a selection handle of the given `type`.
///
@ -97,20 +103,20 @@ abstract class TextSelectionControls {
///
/// Typically displays buttons for copying and pasting text.
///
/// [globalEditableRegion] is the TextField size of the global coordinate system
/// in logical pixels.
/// The [globalEditableRegion] parameter is the TextField size of the global
/// coordinate system in logical pixels.
///
/// [textLineHeight] is the `preferredLineHeight` of the [RenderEditable] we
/// are building a toolbar for.
/// The [textLineHeight] parameter is the [RenderEditable.preferredLineHeight]
/// of the [RenderEditable] we are building a toolbar for.
///
/// The [position] is a general calculation midpoint parameter of the toolbar.
/// If you want more detailed position information, can use [endpoints]
/// to calculate it.
/// The [selectionMidpoint] parameter is a general calculation midpoint
/// parameter of the toolbar. More detailed position information
/// is computable from the [endpoints] parameter.
Widget buildToolbar(
BuildContext context,
Rect globalEditableRegion,
double textLineHeight,
Offset position,
Offset selectionMidpoint,
List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate,
ValueListenable<ClipboardStatus>? clipboardStatus,
@ -207,6 +213,55 @@ abstract class TextSelectionControls {
}
}
/// Text selection controls that do not show any toolbars or handles.
///
/// This is a placeholder, suitable for temporary use during development, but
/// not practical for production. For example, it provides no way for the user
/// to interact with selections: no context menus on desktop, no toolbars or
/// drag handles on mobile, etc. For production, consider using
/// [MaterialTextSelectionControls] or creating a custom subclass of
/// [TextSelectionControls].
///
/// The [emptyTextSelectionControls] global variable has a
/// suitable instance of this class.
class EmptyTextSelectionControls extends TextSelectionControls {
@override
Size getHandleSize(double textLineHeight) => Size.zero;
@override
Widget buildToolbar(
BuildContext context,
Rect globalEditableRegion,
double textLineHeight,
Offset selectionMidpoint,
List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate,
ValueListenable<ClipboardStatus>? clipboardStatus,
Offset? lastSecondaryTapDownPosition,
) => const SizedBox.shrink();
@override
Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight, [VoidCallback? onTap]) {
return const SizedBox.shrink();
}
@override
Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) {
return Offset.zero;
}
}
/// Text selection controls that do not show any toolbars or handles.
///
/// This is a placeholder, suitable for temporary use during development, but
/// not practical for production. For example, it provides no way for the user
/// to interact with selections: no context menus on desktop, no toolbars or
/// drag handles on mobile, etc. For production, consider using
/// [materialTextSelectionControls] or creating a custom subclass of
/// [TextSelectionControls].
final TextSelectionControls emptyTextSelectionControls = EmptyTextSelectionControls();
/// An object that manages a pair of text selection handles for a
/// [RenderEditable].
///

View file

@ -4,7 +4,6 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/editable_text_utils.dart' show textOffsetToPosition;

View file

@ -4,7 +4,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/editable_text_utils.dart' show textOffsetToPosition;

View file

@ -0,0 +1,153 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
const double _crispText = 100.0; // this font size is selected to avoid needing any antialiasing.
const String _expText = 'Éxp'; // renders in Ahem as:
// ########
// ########
// ########
// ########
//
// ÉÉÉÉxxxxpppp
void main() {
testWidgets('Default background', (WidgetTester tester) async {
await tester.pumpWidget(const Align(
alignment: Alignment.topLeft,
child: Text(_expText, textDirection: TextDirection.ltr, style: TextStyle(color: Color(0xFF345678), fontSize: _crispText))),
);
await _expectColors(
tester,
find.byType(Align),
<Color>{ const Color(0x00000000), const Color(0xFF345678) },
<Offset, Color>{
Offset.zero: const Color(0xFF345678), // the text
const Offset(10, 10): const Color(0xFF345678), // the text
const Offset(50, 95): const Color(0x00000000), // the background (under the É)
const Offset(250, 50): const Color(0x00000000), // the text (above the p)
const Offset(250, 95): const Color(0xFF345678), // the text (the p)
const Offset(400, 400): const Color(0x00000000), // the background
const Offset(799, 599): const Color(0x00000000), // the background
},
);
}, skip: !canCaptureImage); // [intended] Test relies on captureImage, which is not supported on web currently.
testWidgets('Default text color', (WidgetTester tester) async {
await tester.pumpWidget(const ColoredBox(
color: Color(0xFFABCDEF),
child: Align(
alignment: Alignment.topLeft,
child: Text('Éxp', textDirection: TextDirection.ltr, style: TextStyle(fontSize: _crispText)),
),
),
);
await _expectColors(
tester,
find.byType(Align),
<Color>{ const Color(0xFFABCDEF), const Color(0xFFFFFFFF) },
<Offset, Color>{
Offset.zero: const Color(0xFFFFFFFF), // the text
const Offset(10, 10): const Color(0xFFFFFFFF), // the text
const Offset(50, 95): const Color(0xFFABCDEF), // the background (under the É)
const Offset(250, 50): const Color(0xFFABCDEF), // the text (above the p)
const Offset(250, 95): const Color(0xFFFFFFFF), // the text (the p)
const Offset(400, 400): const Color(0xFFABCDEF), // the background
const Offset(799, 599): const Color(0xFFABCDEF), // the background
},
);
}, skip: !canCaptureImage); // [intended] Test relies on captureImage, which is not supported on web currently.
testWidgets('Default text selection color', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
ColoredBox(
color: const Color(0xFFFFFFFF),
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) => SelectableRegion(
focusNode: FocusNode(),
selectionControls: emptyTextSelectionControls,
child: Align(
key: key,
alignment: Alignment.topLeft,
child: const Text('Éxp', textDirection: TextDirection.ltr, style: TextStyle(fontSize: _crispText)),
),
),
),
],
),
),
),
),
);
await _expectColors(
tester,
find.byType(Align),
<Color>{ const Color(0xFFFFFFFF) },
);
// fake a "select all" event to selecte the text
Actions.invoke(key.currentContext!, const SelectAllTextIntent(SelectionChangedCause.keyboard));
await tester.pump();
await _expectColors(
tester,
find.byType(Align),
<Color>{ const Color(0xFFFFFFFF), const Color(0xFFBFBFBF) }, // 0x80808080 blended with 0xFFFFFFFF
<Offset, Color>{
Offset.zero: const Color(0xFFBFBFBF), // the selected text
const Offset(10, 10): const Color(0xFFBFBFBF), // the selected text
const Offset(50, 95): const Color(0xFFBFBFBF), // the selected background (under the É)
const Offset(250, 50): const Color(0xFFBFBFBF), // the selected background (above the p)
const Offset(250, 95): const Color(0xFFBFBFBF), // the selected text (the p)
const Offset(400, 400): const Color(0xFFFFFFFF), // the background
const Offset(799, 599): const Color(0xFFFFFFFF), // the background
},
);
}, skip: !canCaptureImage); // [intended] Test relies on captureImage, which is not supported on web currently.
}
Color _getPixel(ByteData bytes, int x, int y, int width) {
final int offset = (x + y * width) * 4;
return Color.fromARGB(
bytes.getUint8(offset + 3),
bytes.getUint8(offset + 0),
bytes.getUint8(offset + 1),
bytes.getUint8(offset + 2),
);
}
Future<void> _expectColors(WidgetTester tester, Finder finder, Set<Color> allowedColors, [ Map<Offset, Color>? spotChecks ]) async {
final TestWidgetsFlutterBinding binding = tester.binding;
final ui.Image image = (await binding.runAsync<ui.Image>(() => captureImage(finder.evaluate().single)))!;
final ByteData bytes = (await binding.runAsync<ByteData?>(() => image.toByteData(format: ui.ImageByteFormat.rawStraightRgba)))!;
final Set<int> actualColorValues = <int>{};
for (int offset = 0; offset < bytes.lengthInBytes; offset += 4) {
actualColorValues.add((bytes.getUint8(offset + 3) << 24) +
(bytes.getUint8(offset + 0) << 16) +
(bytes.getUint8(offset + 1) << 8) +
(bytes.getUint8(offset + 2)));
}
final Set<Color> actualColors = actualColorValues.map((int value) => Color(value)).toSet();
expect(actualColors, allowedColors);
spotChecks?.forEach((Offset position, Color expected) {
assert(position.dx.round() >= 0);
assert(position.dx.round() < image.width);
assert(position.dy.round() >= 0);
assert(position.dy.round() < image.height);
final Offset precisePosition = position * binding.window.devicePixelRatio;
final Color actual = _getPixel(bytes, precisePosition.dx.round(), precisePosition.dy.round(), image.width);
expect(actual, expected, reason: 'Pixel at $position is $actual but expected $expected.');
});
}

View file

@ -56,6 +56,7 @@ library flutter_test;
export 'dart:async' show Future;
export 'src/_goldens_io.dart' if (dart.library.html) 'src/_goldens_web.dart';
export 'src/_matchers_io.dart' if (dart.library.html) 'src/_matchers_web.dart';
export 'src/accessibility.dart';
export 'src/all_elements.dart';
export 'src/animation_sheet.dart';

View file

@ -30,6 +30,15 @@ Future<ui.Image> captureImage(Element element) {
return layer.toImage(renderObject.paintBounds);
}
/// Whether or not [captureImage] is supported.
///
/// This can be used to skip tests on platforms that don't support
/// capturing images.
///
/// Currently this is true except when tests are running in the context of a web
/// browser (`flutter test --platform chrome`).
const bool canCaptureImage = true;
/// The matcher created by [matchesGoldenFile]. This class is enabled when the
/// test is running on a VM using conditional import.
class MatchesGoldenFile extends AsyncMatcher {
@ -84,7 +93,7 @@ class MatchesGoldenFile extends AsyncMatcher {
throw AssertionError('must provide a Finder, Image, Future<Image>, List<int>, or Future<List<int>>');
}
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance;
return binding.runAsync<String?>(() async {
final ui.Image? image = await imageFuture;
if (image == null) {

View file

@ -18,6 +18,15 @@ Future<ui.Image> captureImage(Element element) {
throw UnsupportedError('captureImage is not supported on the web.');
}
/// Whether or not [captureImage] is supported.
///
/// This can be used to skip tests on platforms that don't support
/// capturing images.
///
/// Currently this is true except when tests are running in the context of a web
/// browser (`flutter test --platform chrome`).
const bool canCaptureImage = false;
/// The matcher created by [matchesGoldenFile]. This class is enabled when the
/// test is running in a web browser using conditional import.
class MatchesGoldenFile extends AsyncMatcher {
@ -47,7 +56,7 @@ class MatchesGoldenFile extends AsyncMatcher {
final Element element = elements.single;
final RenderObject renderObject = _findRepaintBoundary(element);
final Size size = renderObject.paintBounds.size;
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance;
final Element e = binding.renderViewElement!;
// Unlike `flutter_tester`, we don't have the ability to render an element

View file

@ -1131,28 +1131,45 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
addTime(additionalTime);
return realAsyncZone.run<Future<T?>>(() async {
return realAsyncZone.run<Future<T?>>(() {
final Completer<T?> result = Completer<T?>();
_pendingAsyncTasks = Completer<void>();
T? result;
try {
result = await callback();
callback().then(result.complete).catchError(
(Object exception, StackTrace stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'Flutter test framework',
context: ErrorDescription('while running async test code'),
informationCollector: () {
return <DiagnosticsNode>[
ErrorHint('The exception was caught asynchronously.'),
];
},
));
result.complete(null);
},
);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'Flutter test framework',
context: ErrorDescription('while running async test code'),
informationCollector: () {
return <DiagnosticsNode>[
ErrorHint('The exception was caught synchronously.'),
];
},
));
} finally {
// We complete the _pendingAsyncTasks future successfully regardless of
// whether an exception occurred because in the case of an exception,
// we already reported the exception to FlutterError. Moreover,
// completing the future with an error would trigger an unhandled
// exception due to zone error boundaries.
result.complete(null);
}
result.future.whenComplete(() {
_pendingAsyncTasks!.complete();
_pendingAsyncTasks = null;
}
return result;
});
return result.future;
});
}

View file

@ -60,11 +60,11 @@ class _BufferGoldenMatcher extends AsyncMatcher {
/// golden files. This parameter is optional.
///
/// {@tool snippet}
/// Sample invocations of [matchesGoldenFile].
/// Sample invocations of [bufferMatchesGoldenFile].
///
/// ```dart
/// await expectLater(
/// const <int>[],
/// const <int>[ /* bytes... */ ],
/// bufferMatchesGoldenFile('sample.png'),
/// );
/// ```

View file

@ -2030,7 +2030,7 @@ class _MatchesReferenceImage extends AsyncMatcher {
imageFuture = captureImage(elements.single);
}
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance;
return binding.runAsync<String?>(() async {
final ui.Image image = await imageFuture;
final ByteData? bytes = await image.toByteData();

View file

@ -630,6 +630,23 @@ void main() {
key: 'abczed',
});
});
testWidgets('control test (return value)', (WidgetTester tester) async {
final String? result = await tester.binding.runAsync<String>(() async => 'Judy Turner');
expect(result, 'Judy Turner');
});
testWidgets('async throw', (WidgetTester tester) async {
final String? result = await tester.binding.runAsync<Never>(() async => throw Exception('Lois Dilettente'));
expect(result, isNull);
expect(tester.takeException(), isNotNull);
});
testWidgets('sync throw', (WidgetTester tester) async {
final String? result = await tester.binding.runAsync<Never>(() => throw Exception('Butch Barton'));
expect(result, isNull);
expect(tester.takeException(), isNotNull);
});
});
group('showKeyboard', () {