Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
24e195d9
Unverified
Commit
24e195d9
authored
Jan 20, 2021
by
Justin McCandless
Committed by
GitHub
Jan 20, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Mac context menu (#73882)
A very minimal right-click menu for Mac desktop.
parent
c66596e5
Changes
23
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
1268 additions
and
107 deletions
+1268
-107
cupertino.dart
packages/flutter/lib/cupertino.dart
+1
-0
desktop_text_selection.dart
...ges/flutter/lib/src/cupertino/desktop_text_selection.dart
+455
-0
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+18
-1
text_selection.dart
packages/flutter/lib/src/cupertino/text_selection.dart
+21
-25
text_selection_toolbar_button.dart
...tter/lib/src/cupertino/text_selection_toolbar_button.dart
+4
-0
selectable_text.dart
packages/flutter/lib/src/material/selectable_text.dart
+12
-1
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+12
-1
text_selection.dart
packages/flutter/lib/src/material/text_selection.dart
+1
-0
text_selection_toolbar.dart
...ages/flutter/lib/src/material/text_selection_toolbar.dart
+0
-10
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+14
-0
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+69
-0
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+151
-8
text_selection_test.dart
packages/flutter/test/cupertino/text_selection_test.dart
+1
-1
text_selection_toolbar_test.dart
...s/flutter/test/cupertino/text_selection_toolbar_test.dart
+2
-1
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+296
-5
text_form_field_test.dart
packages/flutter/test/material/text_form_field_test.dart
+104
-0
text_selection_test.dart
packages/flutter/test/material/text_selection_test.dart
+1
-1
text_selection_toolbar_test.dart
...es/flutter/test/material/text_selection_toolbar_test.dart
+2
-1
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+1
-1
editable_text_utils.dart
packages/flutter/test/widgets/editable_text_utils.dart
+22
-0
selectable_text_test.dart
packages/flutter/test/widgets/selectable_text_test.dart
+80
-3
text.dart
packages/flutter/test/widgets/text.dart
+0
-47
text_selection_test.dart
packages/flutter/test/widgets/text_selection_test.dart
+1
-1
No files found.
packages/flutter/lib/cupertino.dart
View file @
24e195d9
...
...
@@ -32,6 +32,7 @@ export 'src/cupertino/constants.dart';
export
'src/cupertino/context_menu.dart'
;
export
'src/cupertino/context_menu_action.dart'
;
export
'src/cupertino/date_picker.dart'
;
export
'src/cupertino/desktop_text_selection.dart'
;
export
'src/cupertino/dialog.dart'
;
export
'src/cupertino/form_row.dart'
;
export
'src/cupertino/form_section.dart'
;
...
...
packages/flutter/lib/src/cupertino/desktop_text_selection.dart
0 → 100644
View file @
24e195d9
// 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/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button.dart'
;
import
'colors.dart'
;
import
'localizations.dart'
;
import
'theme.dart'
;
// Minimal padding from all edges of the selection toolbar to all edges of the
// screen.
const
double
_kToolbarScreenPadding
=
8.0
;
// These values were measured from a screenshot of TextEdit on MacOS 10.15.7 on
// a Macbook Pro.
const
double
_kToolbarWidth
=
222.0
;
const
Radius
_kToolbarBorderRadius
=
Radius
.
circular
(
4.0
);
// These values were measured from a screenshot of TextEdit on MacOS 10.16 on a
// Macbook Pro.
const
CupertinoDynamicColor
_kToolbarBorderColor
=
CupertinoDynamicColor
.
withBrightness
(
color:
Color
(
0xFFBBBBBB
),
darkColor:
Color
(
0xFF505152
),
);
const
CupertinoDynamicColor
_kToolbarBackgroundColor
=
CupertinoDynamicColor
.
withBrightness
(
color:
Color
(
0xffECE8E6
),
darkColor:
Color
(
0xff302928
),
);
class
_CupertinoDesktopTextSelectionControls
extends
TextSelectionControls
{
/// Desktop has no text selection handles.
@override
Size
getHandleSize
(
double
textLineHeight
)
{
return
Size
.
zero
;
}
/// Builder for the Mac-style copy/paste text selection toolbar.
@override
Widget
buildToolbar
(
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
selectionMidpoint
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
return
_CupertinoDesktopTextSelectionControlsToolbar
(
clipboardStatus:
clipboardStatus
,
endpoints:
endpoints
,
globalEditableRegion:
globalEditableRegion
,
handleCut:
canCut
(
delegate
)
?
()
=>
handleCut
(
delegate
)
:
null
,
handleCopy:
canCopy
(
delegate
)
?
()
=>
handleCopy
(
delegate
,
clipboardStatus
)
:
null
,
handlePaste:
canPaste
(
delegate
)
?
()
=>
handlePaste
(
delegate
)
:
null
,
handleSelectAll:
canSelectAll
(
delegate
)
?
()
=>
handleSelectAll
(
delegate
)
:
null
,
selectionMidpoint:
selectionMidpoint
,
lastSecondaryTapDownPosition:
lastSecondaryTapDownPosition
,
textLineHeight:
textLineHeight
,
);
}
/// Builds the text selection handles, but desktop has none.
@override
Widget
buildHandle
(
BuildContext
context
,
TextSelectionHandleType
type
,
double
textLineHeight
)
{
return
const
SizedBox
.
shrink
();
}
/// Gets the position for the text selection handles, but desktop has none.
@override
Offset
getHandleAnchor
(
TextSelectionHandleType
type
,
double
textLineHeight
)
{
return
Offset
.
zero
;
}
}
/// Text selection controls that follows Mac design conventions.
final
TextSelectionControls
cupertinoDesktopTextSelectionControls
=
_CupertinoDesktopTextSelectionControls
();
// Generates the child that's passed into CupertinoDesktopTextSelectionToolbar.
class
_CupertinoDesktopTextSelectionControlsToolbar
extends
StatefulWidget
{
const
_CupertinoDesktopTextSelectionControlsToolbar
({
Key
?
key
,
required
this
.
clipboardStatus
,
required
this
.
endpoints
,
required
this
.
globalEditableRegion
,
required
this
.
handleCopy
,
required
this
.
handleCut
,
required
this
.
handlePaste
,
required
this
.
handleSelectAll
,
required
this
.
selectionMidpoint
,
required
this
.
textLineHeight
,
required
this
.
lastSecondaryTapDownPosition
,
})
:
super
(
key:
key
);
final
ClipboardStatusNotifier
?
clipboardStatus
;
final
List
<
TextSelectionPoint
>
endpoints
;
final
Rect
globalEditableRegion
;
final
VoidCallback
?
handleCopy
;
final
VoidCallback
?
handleCut
;
final
VoidCallback
?
handlePaste
;
final
VoidCallback
?
handleSelectAll
;
final
Offset
?
lastSecondaryTapDownPosition
;
final
Offset
selectionMidpoint
;
final
double
textLineHeight
;
@override
_CupertinoDesktopTextSelectionControlsToolbarState
createState
()
=>
_CupertinoDesktopTextSelectionControlsToolbarState
();
}
class
_CupertinoDesktopTextSelectionControlsToolbarState
extends
State
<
_CupertinoDesktopTextSelectionControlsToolbar
>
{
ClipboardStatusNotifier
?
_clipboardStatus
;
void
_onChangedClipboardStatus
()
{
setState
(()
{
// Inform the widget that the value of clipboardStatus has changed.
});
}
@override
void
initState
()
{
super
.
initState
();
if
(
widget
.
handlePaste
!=
null
)
{
_clipboardStatus
=
widget
.
clipboardStatus
??
ClipboardStatusNotifier
();
_clipboardStatus
!.
addListener
(
_onChangedClipboardStatus
);
_clipboardStatus
!.
update
();
}
}
@override
void
didUpdateWidget
(
_CupertinoDesktopTextSelectionControlsToolbar
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
clipboardStatus
!=
widget
.
clipboardStatus
)
{
if
(
_clipboardStatus
!=
null
)
{
_clipboardStatus
!.
removeListener
(
_onChangedClipboardStatus
);
_clipboardStatus
!.
dispose
();
}
_clipboardStatus
=
widget
.
clipboardStatus
??
ClipboardStatusNotifier
();
_clipboardStatus
!.
addListener
(
_onChangedClipboardStatus
);
if
(
widget
.
handlePaste
!=
null
)
{
_clipboardStatus
!.
update
();
}
}
}
@override
void
dispose
()
{
super
.
dispose
();
// When used in an Overlay, this can be disposed after its creator has
// already disposed _clipboardStatus.
if
(
_clipboardStatus
!=
null
&&
!
_clipboardStatus
!.
disposed
)
{
_clipboardStatus
!.
removeListener
(
_onChangedClipboardStatus
);
if
(
widget
.
clipboardStatus
==
null
)
{
_clipboardStatus
!.
dispose
();
}
}
}
@override
Widget
build
(
BuildContext
context
)
{
// Don't render the menu until the state of the clipboard is known.
if
(
widget
.
handlePaste
!=
null
&&
_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
(
(
widget
.
selectionMidpoint
.
dx
-
widget
.
globalEditableRegion
.
left
).
clamp
(
mediaQuery
.
padding
.
left
,
mediaQuery
.
size
.
width
-
mediaQuery
.
padding
.
right
,
),
widget
.
selectionMidpoint
.
dy
-
widget
.
globalEditableRegion
.
top
,
);
final
List
<
Widget
>
items
=
<
Widget
>[];
final
CupertinoLocalizations
localizations
=
CupertinoLocalizations
.
of
(
context
);
final
Widget
onePhysicalPixelVerticalDivider
=
SizedBox
(
width:
1.0
/
MediaQuery
.
of
(
context
).
devicePixelRatio
);
void
addToolbarButton
(
String
text
,
VoidCallback
onPressed
,
)
{
if
(
items
.
isNotEmpty
)
{
items
.
add
(
onePhysicalPixelVerticalDivider
);
}
items
.
add
(
_CupertinoDesktopTextSelectionToolbarButton
.
text
(
context:
context
,
onPressed:
onPressed
,
text:
text
,
));
}
if
(
widget
.
handleCut
!=
null
)
{
addToolbarButton
(
localizations
.
cutButtonLabel
,
widget
.
handleCut
!);
}
if
(
widget
.
handleCopy
!=
null
)
{
addToolbarButton
(
localizations
.
copyButtonLabel
,
widget
.
handleCopy
!);
}
if
(
widget
.
handlePaste
!=
null
&&
_clipboardStatus
!.
value
==
ClipboardStatus
.
pasteable
)
{
addToolbarButton
(
localizations
.
pasteButtonLabel
,
widget
.
handlePaste
!);
}
if
(
widget
.
handleSelectAll
!=
null
)
{
addToolbarButton
(
localizations
.
selectAllButtonLabel
,
widget
.
handleSelectAll
!);
}
// If there is no option available, build an empty widget.
if
(
items
.
isEmpty
)
{
return
const
SizedBox
(
width:
0.0
,
height:
0.0
);
}
return
_CupertinoDesktopTextSelectionToolbar
(
anchor:
widget
.
lastSecondaryTapDownPosition
??
midpointAnchor
,
children:
items
,
);
}
}
/// A Mac-style text selection toolbar.
///
/// Typically displays buttons for text manipulation, e.g. copying and pasting
/// text.
///
/// Tries to position itself as closesly as possible to [anchor] while remaining
/// fully on-screen.
///
/// See also:
///
/// * [TextSelectionControls.buildToolbar], where this is used by default to
/// build a Mac-style toolbar.
/// * [TextSelectionToolbar], which is similar, but builds an Android-style
/// toolbar.
class
_CupertinoDesktopTextSelectionToolbar
extends
StatelessWidget
{
/// Creates an instance of CupertinoTextSelectionToolbar.
const
_CupertinoDesktopTextSelectionToolbar
({
Key
?
key
,
required
this
.
anchor
,
required
this
.
children
,
this
.
toolbarBuilder
=
_defaultToolbarBuilder
,
})
:
assert
(
children
.
length
>
0
),
super
(
key:
key
);
/// The point at which the toolbar will attempt to position itself as closely
/// as possible.
final
Offset
anchor
;
/// {@macro flutter.material.TextSelectionToolbar.children}
///
/// See also:
/// * [CupertinoDesktopTextSelectionToolbarButton], which builds a default
/// Mac-style text selection toolbar text button.
final
List
<
Widget
>
children
;
/// {@macro flutter.material.TextSelectionToolbar.toolbarBuilder}
///
/// The given anchor and isAbove can be used to position an arrow, as in the
/// default Cupertino toolbar.
final
ToolbarBuilder
toolbarBuilder
;
// Builds a toolbar just like the default Mac toolbar, with the right color
// background, padding, and rounded corners.
static
Widget
_defaultToolbarBuilder
(
BuildContext
context
,
Widget
child
)
{
return
Container
(
width:
_kToolbarWidth
,
decoration:
BoxDecoration
(
color:
_kToolbarBackgroundColor
.
resolveFrom
(
context
),
border:
Border
.
all
(
color:
_kToolbarBorderColor
.
resolveFrom
(
context
),
),
borderRadius:
const
BorderRadius
.
all
(
_kToolbarBorderRadius
),
),
child:
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
0.0
,
// This value was measured from a screenshot of TextEdit on MacOS
// 10.15.7 on a Macbook Pro.
vertical:
3.0
,
),
child:
child
,
),
);
}
@override
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
final
MediaQueryData
mediaQuery
=
MediaQuery
.
of
(
context
);
final
double
paddingAbove
=
mediaQuery
.
padding
.
top
+
_kToolbarScreenPadding
;
final
Offset
localAdjustment
=
Offset
(
_kToolbarScreenPadding
,
paddingAbove
);
return
Padding
(
padding:
EdgeInsets
.
fromLTRB
(
_kToolbarScreenPadding
,
paddingAbove
,
_kToolbarScreenPadding
,
_kToolbarScreenPadding
,
),
child:
CustomSingleChildLayout
(
delegate:
_DesktopTextSelectionToolbarLayoutDelegate
(
anchor:
anchor
-
localAdjustment
,
),
child:
toolbarBuilder
(
context
,
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
children
,
)),
),
);
}
}
// Positions the toolbar at [anchor] if it fits, otherwise moves it so that it
// just fits fully on-screen.
//
// See also:
//
// * [CupertinoDesktopTextSelectionToolbar], which uses this to position itself.
// * [TextSelectionToolbarLayoutDelegate], which does a similar layout for
// the mobile text selection toolbars.
class
_DesktopTextSelectionToolbarLayoutDelegate
extends
SingleChildLayoutDelegate
{
/// Creates an instance of TextSelectionToolbarLayoutDelegate.
_DesktopTextSelectionToolbarLayoutDelegate
({
required
this
.
anchor
,
});
/// The point at which to render the menu, if possible.
///
/// Should be provided in local coordinates.
final
Offset
anchor
;
@override
BoxConstraints
getConstraintsForChild
(
BoxConstraints
constraints
)
{
return
constraints
.
loosen
();
}
@override
Offset
getPositionForChild
(
Size
size
,
Size
childSize
)
{
final
Offset
overhang
=
Offset
(
anchor
.
dx
+
childSize
.
width
-
size
.
width
,
anchor
.
dy
+
childSize
.
height
-
size
.
height
,
);
return
Offset
(
overhang
.
dx
>
0.0
?
anchor
.
dx
-
overhang
.
dx
:
anchor
.
dx
,
overhang
.
dy
>
0.0
?
anchor
.
dy
-
overhang
.
dy
:
anchor
.
dy
,
);
}
@override
bool
shouldRelayout
(
_DesktopTextSelectionToolbarLayoutDelegate
oldDelegate
)
{
return
anchor
!=
oldDelegate
.
anchor
;
}
}
// These values were measured from a screenshot of TextEdit on MacOS 10.15.7 on
// a Macbook Pro.
const
TextStyle
_kToolbarButtonFontStyle
=
TextStyle
(
inherit:
false
,
fontSize:
14.0
,
letterSpacing:
-
0.15
,
fontWeight:
FontWeight
.
w400
,
);
// This value was measured from a screenshot of TextEdit on MacOS 10.15.7 on a
// Macbook Pro.
const
EdgeInsets
_kToolbarButtonPadding
=
EdgeInsets
.
fromLTRB
(
20.0
,
0.0
,
20.0
,
3.0
,
);
/// A button in the style of the Mac context menu buttons.
class
_CupertinoDesktopTextSelectionToolbarButton
extends
StatefulWidget
{
/// Creates an instance of CupertinoDesktopTextSelectionToolbarButton.
const
_CupertinoDesktopTextSelectionToolbarButton
({
Key
?
key
,
required
this
.
onPressed
,
required
this
.
child
,
})
:
super
(
key:
key
);
/// Create an instance of [CupertinoDesktopTextSelectionToolbarButton] whose child is
/// a [Text] widget styled like the default Mac context menu button.
_CupertinoDesktopTextSelectionToolbarButton
.
text
({
Key
?
key
,
required
BuildContext
context
,
required
this
.
onPressed
,
required
String
text
,
})
:
child
=
Text
(
text
,
overflow:
TextOverflow
.
ellipsis
,
style:
_kToolbarButtonFontStyle
.
copyWith
(
color:
const
CupertinoDynamicColor
.
withBrightness
(
color:
CupertinoColors
.
black
,
darkColor:
CupertinoColors
.
white
,
).
resolveFrom
(
context
),
),
),
super
(
key:
key
);
/// {@macro flutter.cupertino.CupertinoTextSelectionToolbarButton.onPressed}
final
VoidCallback
onPressed
;
/// {@macro flutter.cupertino.CupertinoTextSelectionToolbarButton.child}
final
Widget
child
;
@override
_CupertinoDesktopTextSelectionToolbarButtonState
createState
()
=>
_CupertinoDesktopTextSelectionToolbarButtonState
();
}
class
_CupertinoDesktopTextSelectionToolbarButtonState
extends
State
<
_CupertinoDesktopTextSelectionToolbarButton
>
{
bool
_isHovered
=
false
;
void
_onEnter
(
PointerEnterEvent
event
)
{
setState
(()
{
_isHovered
=
true
;
});
}
void
_onExit
(
PointerExitEvent
event
)
{
setState
(()
{
_isHovered
=
false
;
});
}
@override
Widget
build
(
BuildContext
context
)
{
return
SizedBox
(
width:
double
.
infinity
,
child:
MouseRegion
(
onEnter:
_onEnter
,
onExit:
_onExit
,
child:
CupertinoButton
(
alignment:
Alignment
.
centerLeft
,
borderRadius:
null
,
color:
_isHovered
?
CupertinoTheme
.
of
(
context
).
primaryColor
:
null
,
minSize:
0.0
,
onPressed:
widget
.
onPressed
,
padding:
_kToolbarButtonPadding
,
pressedOpacity:
0.7
,
child:
widget
.
child
,
),
),
);
}
}
packages/flutter/lib/src/cupertino/text_field.dart
View file @
24e195d9
...
...
@@ -4,12 +4,14 @@
import
'dart:ui'
as
ui
show
BoxHeightStyle
,
BoxWidthStyle
;
import
'package:flutter/foundation.dart'
show
defaultTargetPlatform
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
import
'desktop_text_selection.dart'
;
import
'icons.dart'
;
import
'text_selection.dart'
;
import
'theme.dart'
;
...
...
@@ -1064,7 +1066,22 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
super
.
build
(
context
);
// See AutomaticKeepAliveClientMixin.
assert
(
debugCheckHasDirectionality
(
context
));
final
TextEditingController
controller
=
_effectiveController
;
final
TextSelectionControls
textSelectionControls
=
widget
.
selectionControls
??
cupertinoTextSelectionControls
;
TextSelectionControls
?
textSelectionControls
=
widget
.
selectionControls
;
switch
(
defaultTargetPlatform
)
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
windows
:
textSelectionControls
??=
cupertinoTextSelectionControls
;
break
;
case
TargetPlatform
.
macOS
:
textSelectionControls
??=
cupertinoDesktopTextSelectionControls
;
break
;
}
final
bool
enabled
=
widget
.
enabled
??
true
;
final
Offset
cursorOffset
=
Offset
(
_iOSHorizontalCursorOffsetPixels
/
MediaQuery
.
of
(
context
).
devicePixelRatio
,
0
);
final
List
<
TextInputFormatter
>
formatters
=
<
TextInputFormatter
>[
...
...
packages/flutter/lib/src/cupertino/text_selection.dart
View file @
24e195d9
...
...
@@ -52,7 +52,7 @@ class _CupertinoTextSelectionControlsToolbar extends StatefulWidget {
}
class
_CupertinoTextSelectionControlsToolbarState
extends
State
<
_CupertinoTextSelectionControlsToolbar
>
{
late
ClipboardStatusNotifier
_clipboardStatus
;
ClipboardStatusNotifier
?
_clipboardStatus
;
void
_onChangedClipboardStatus
()
{
setState
(()
{
...
...
@@ -63,31 +63,26 @@ class _CupertinoTextSelectionControlsToolbarState extends State<_CupertinoTextSe
@override
void
initState
()
{
super
.
initState
();
_clipboardStatus
=
widget
.
clipboardStatus
??
ClipboardStatusNotifier
();
_clipboardStatus
.
addListener
(
_onChangedClipboardStatus
);
_clipboardStatus
.
update
();
if
(
widget
.
handlePaste
!=
null
)
{
_clipboardStatus
=
widget
.
clipboardStatus
??
ClipboardStatusNotifier
();
_clipboardStatus
!.
addListener
(
_onChangedClipboardStatus
);
_clipboardStatus
!.
update
();
}
}
@override
void
didUpdateWidget
(
_CupertinoTextSelectionControlsToolbar
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
clipboardStatus
==
null
&&
widget
.
clipboardStatus
!=
null
)
{
_clipboardStatus
.
removeListener
(
_onChangedClipboardStatus
);
_clipboardStatus
.
dispose
();
_clipboardStatus
=
widget
.
clipboardStatus
!;
}
else
if
(
oldWidget
.
clipboardStatus
!=
null
)
{
if
(
widget
.
clipboardStatus
==
null
)
{
_clipboardStatus
=
ClipboardStatusNotifier
();
_clipboardStatus
.
addListener
(
_onChangedClipboardStatus
);
oldWidget
.
clipboardStatus
!.
removeListener
(
_onChangedClipboardStatus
);
}
else
if
(
widget
.
clipboardStatus
!=
oldWidget
.
clipboardStatus
)
{
_clipboardStatus
=
widget
.
clipboardStatus
!;
_clipboardStatus
.
addListener
(
_onChangedClipboardStatus
);
oldWidget
.
clipboardStatus
!.
removeListener
(
_onChangedClipboardStatus
);
if
(
oldWidget
.
clipboardStatus
!=
widget
.
clipboardStatus
)
{
if
(
_clipboardStatus
!=
null
)
{
_clipboardStatus
!.
removeListener
(
_onChangedClipboardStatus
);
_clipboardStatus
!.
dispose
();
}
_clipboardStatus
=
widget
.
clipboardStatus
??
ClipboardStatusNotifier
();
_clipboardStatus
!.
addListener
(
_onChangedClipboardStatus
);
if
(
widget
.
handlePaste
!=
null
)
{
_clipboardStatus
!.
update
();
}
}
if
(
widget
.
handlePaste
!=
null
)
{
_clipboardStatus
.
update
();
}
}
...
...
@@ -96,10 +91,10 @@ class _CupertinoTextSelectionControlsToolbarState extends State<_CupertinoTextSe
super
.
dispose
();
// When used in an Overlay, this can be disposed after its creator has
// already disposed _clipboardStatus.
if
(
!
_clipboardStatus
.
disposed
)
{
_clipboardStatus
.
removeListener
(
_onChangedClipboardStatus
);
if
(
_clipboardStatus
!=
null
&&
!
_clipboardStatus
!
.
disposed
)
{
_clipboardStatus
!
.
removeListener
(
_onChangedClipboardStatus
);
if
(
widget
.
clipboardStatus
==
null
)
{
_clipboardStatus
.
dispose
();
_clipboardStatus
!
.
dispose
();
}
}
}
...
...
@@ -108,7 +103,7 @@ class _CupertinoTextSelectionControlsToolbarState extends State<_CupertinoTextSe
Widget
build
(
BuildContext
context
)
{
// Don't render the menu until the state of the clipboard is known.
if
(
widget
.
handlePaste
!=
null
&&
_clipboardStatus
.
value
==
ClipboardStatus
.
unknown
)
{
&&
_clipboardStatus
!
.
value
==
ClipboardStatus
.
unknown
)
{
return
const
SizedBox
(
width:
0.0
,
height:
0.0
);
}
...
...
@@ -162,7 +157,7 @@ class _CupertinoTextSelectionControlsToolbarState extends State<_CupertinoTextSe
addToolbarButton
(
localizations
.
copyButtonLabel
,
widget
.
handleCopy
!);
}
if
(
widget
.
handlePaste
!=
null
&&
_clipboardStatus
.
value
==
ClipboardStatus
.
pasteable
)
{
&&
_clipboardStatus
!
.
value
==
ClipboardStatus
.
pasteable
)
{
addToolbarButton
(
localizations
.
pasteButtonLabel
,
widget
.
handlePaste
!);
}
if
(
widget
.
handleSelectAll
!=
null
)
{
...
...
@@ -235,6 +230,7 @@ class CupertinoTextSelectionControls extends TextSelectionControls {
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
return
_CupertinoTextSelectionControlsToolbar
(
clipboardStatus:
clipboardStatus
,
...
...
packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart
View file @
24e195d9
...
...
@@ -45,12 +45,16 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget {
),
super
(
key:
key
);
/// {@template flutter.cupertino.CupertinoTextSelectionToolbarButton.child}
/// The child of this button.
///
/// Usually a [Text] or an [Icon].
/// {@endtemplate}
final
Widget
child
;
/// {@template flutter.cupertino.CupertinoTextSelectionToolbarButton.onPressed}
/// Called when this button is pressed.
/// {@endtemplate}
final
VoidCallback
?
onPressed
;
@override
...
...
packages/flutter/lib/src/material/selectable_text.dart
View file @
24e195d9
...
...
@@ -581,7 +581,6 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
switch
(
theme
.
platform
)
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
final
CupertinoThemeData
cupertinoTheme
=
CupertinoTheme
.
of
(
context
);
forcePressEnabled
=
true
;
textSelectionControls
??=
cupertinoTextSelectionControls
;
...
...
@@ -593,6 +592,18 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
cursorOffset
=
Offset
(
iOSHorizontalOffset
/
MediaQuery
.
of
(
context
).
devicePixelRatio
,
0
);
break
;
case
TargetPlatform
.
macOS
:
final
CupertinoThemeData
cupertinoTheme
=
CupertinoTheme
.
of
(
context
);
forcePressEnabled
=
false
;
textSelectionControls
??=
cupertinoDesktopTextSelectionControls
;
paintCursorAboveText
=
true
;
cursorOpacityAnimates
=
true
;
cursorColor
??=
selectionTheme
.
cursorColor
??
cupertinoTheme
.
primaryColor
;
selectionColor
=
selectionTheme
.
selectionColor
??
cupertinoTheme
.
primaryColor
.
withOpacity
(
0.40
);
cursorRadius
??=
const
Radius
.
circular
(
2.0
);
cursorOffset
=
Offset
(
iOSHorizontalOffset
/
MediaQuery
.
of
(
context
).
devicePixelRatio
,
0
);
break
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
24e195d9
...
...
@@ -1143,7 +1143,6 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
switch
(
theme
.
platform
)
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
final
CupertinoThemeData
cupertinoTheme
=
CupertinoTheme
.
of
(
context
);
forcePressEnabled
=
true
;
textSelectionControls
??=
cupertinoTextSelectionControls
;
...
...
@@ -1156,6 +1155,18 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
autocorrectionTextRectColor
=
selectionColor
;
break
;
case
TargetPlatform
.
macOS
:
final
CupertinoThemeData
cupertinoTheme
=
CupertinoTheme
.
of
(
context
);
forcePressEnabled
=
false
;
textSelectionControls
??=
cupertinoDesktopTextSelectionControls
;
paintCursorAboveText
=
true
;
cursorOpacityAnimates
=
true
;
cursorColor
??=
selectionTheme
.
cursorColor
??
cupertinoTheme
.
primaryColor
;
selectionColor
=
selectionTheme
.
selectionColor
??
cupertinoTheme
.
primaryColor
.
withOpacity
(
0.40
);
cursorRadius
??=
const
Radius
.
circular
(
2.0
);
cursorOffset
=
Offset
(
iOSHorizontalOffset
/
MediaQuery
.
of
(
context
).
devicePixelRatio
,
0
);
break
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
...
...
packages/flutter/lib/src/material/text_selection.dart
View file @
24e195d9
...
...
@@ -38,6 +38,7 @@ class MaterialTextSelectionControls extends TextSelectionControls {
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
return
_TextSelectionControlsToolbar
(
globalEditableRegion:
globalEditableRegion
,
...
...
packages/flutter/lib/src/material/text_selection_toolbar.dart
View file @
24e195d9
...
...
@@ -19,16 +19,6 @@ import 'material_localizations.dart';
const
double
_kToolbarScreenPadding
=
8.0
;
const
double
_kToolbarHeight
=
44.0
;
/// The type for a Function that builds a toolbar's container with the given
/// child.
///
/// See also:
///
/// * [TextSelectionToolbar.toolbarBuilder], which is of this type.
/// * [CupertinoTextSelectionToolbar.toolbarBuilder], which is similar, but
/// for a Cupertino-style toolbar.
typedef
ToolbarBuilder
=
Widget
Function
(
BuildContext
context
,
Widget
child
);
/// A fully-functional Material-style text selection toolbar.
///
/// Tries to position itself above [anchorAbove], but if it doesn't fit, then
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
24e195d9
...
...
@@ -1783,6 +1783,20 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
}
Offset
?
_lastTapDownPosition
;
Offset
?
_lastSecondaryTapDownPosition
;
/// The position of the most recent secondary tap down event on this text
/// input.
Offset
?
get
lastSecondaryTapDownPosition
=>
_lastSecondaryTapDownPosition
;
/// Tracks the position of a secondary tap event.
///
/// Should be called before attempting to change the selection based on the
/// position of a secondary tap.
void
handleSecondaryTapDown
(
TapDownDetails
details
)
{
_lastTapDownPosition
=
details
.
globalPosition
;
_lastSecondaryTapDownPosition
=
details
.
globalPosition
;
}
/// If [ignorePointer] is false (the default) then this method is called by
/// the internal gesture recognizer's [TapGestureRecognizer.onTapDown]
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
24e195d9
...
...
@@ -78,6 +78,17 @@ enum _TextSelectionHandlePosition { start, end }
/// having to store the start position.
typedef
DragSelectionUpdateCallback
=
void
Function
(
DragStartDetails
startDetails
,
DragUpdateDetails
updateDetails
);
/// The type for a Function that builds a toolbar's container with the given
/// child.
///
/// See also:
///
/// * [TextSelectionToolbar.toolbarBuilder], which is of this type.
/// type.
/// * [CupertinoTextSelectionToolbar.toolbarBuilder], which is similar, but
/// for a Cupertino-style toolbar.
typedef
ToolbarBuilder
=
Widget
Function
(
BuildContext
context
,
Widget
child
);
/// ParentData that determines whether or not to paint the corresponding child.
///
/// Used in the layout of the Cupertino and Material text selection menus, which
...
...
@@ -131,6 +142,7 @@ abstract class TextSelectionControls {
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
);
/// Returns the size of the selection handle.
...
...
@@ -585,6 +597,7 @@ class TextSelectionOverlay {
endpoints
,
selectionDelegate
!,
clipboardStatus
!,
renderObject
.
lastSecondaryTapDownPosition
,
);
},
),
...
...
@@ -894,6 +907,21 @@ class TextSelectionGestureDetectorBuilder {
@protected
final
TextSelectionGestureDetectorBuilderDelegate
delegate
;
/// Returns true iff lastSecondaryTapDownPosition was on selection.
bool
get
_lastSecondaryTapWasOnSelection
{
assert
(
renderEditable
.
lastSecondaryTapDownPosition
!=
null
);
if
(
renderEditable
.
selection
==
null
)
{
return
false
;
}
final
TextPosition
textPosition
=
renderEditable
.
getPositionForPoint
(
renderEditable
.
lastSecondaryTapDownPosition
!,
);
return
renderEditable
.
selection
!.
base
.
offset
<=
textPosition
.
offset
&&
renderEditable
.
selection
!.
extent
.
offset
>=
textPosition
.
offset
;
}
/// Whether to show the selection toolbar.
///
/// It is based on the signal source when a [onTapDown] is called. This getter
...
...
@@ -1056,6 +1084,35 @@ class TextSelectionGestureDetectorBuilder {
editableText
.
showToolbar
();
}
/// Handler for [TextSelectionGestureDetector.onSecondaryTap].
///
/// By default, selects the word if possible and shows the toolbar.
@protected
void
onSecondaryTap
()
{
if
(
delegate
.
selectionEnabled
)
{
if
(!
_lastSecondaryTapWasOnSelection
)
{
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
tap
);
}
if
(
shouldShowSelectionToolbar
)
{
editableText
.
hideToolbar
();
editableText
.
showToolbar
();
}
}
}
/// Handler for [TextSelectionGestureDetector.onSecondaryTapDown].
///
/// See also:
///
/// * [TextSelectionGestureDetector.onSecondaryTapDown], which triggers this
/// callback.
/// * [onSecondaryTap], which is typically called after this.
@protected
void
onSecondaryTapDown
(
TapDownDetails
details
)
{
renderEditable
.
handleSecondaryTapDown
(
details
);
_shouldShowSelectionToolbar
=
true
;
}
/// Handler for [TextSelectionGestureDetector.onDoubleTapDown].
///
/// By default, it selects a word through [RenderEditable.selectWord] if
...
...
@@ -1142,6 +1199,8 @@ class TextSelectionGestureDetectorBuilder {
onTapDown:
onTapDown
,
onForcePressStart:
delegate
.
forcePressEnabled
?
onForcePressStart
:
null
,
onForcePressEnd:
delegate
.
forcePressEnabled
?
onForcePressEnd
:
null
,
onSecondaryTap:
onSecondaryTap
,
onSecondaryTapDown:
onSecondaryTapDown
,
onSingleTapUp:
onSingleTapUp
,
onSingleTapCancel:
onSingleTapCancel
,
onSingleLongTapStart:
onSingleLongTapStart
,
...
...
@@ -1179,6 +1238,8 @@ class TextSelectionGestureDetector extends StatefulWidget {
this
.
onTapDown
,
this
.
onForcePressStart
,
this
.
onForcePressEnd
,
this
.
onSecondaryTap
,
this
.
onSecondaryTapDown
,
this
.
onSingleTapUp
,
this
.
onSingleTapCancel
,
this
.
onSingleLongTapStart
,
...
...
@@ -1206,6 +1267,12 @@ class TextSelectionGestureDetector extends StatefulWidget {
/// lifted off the screen.
final
GestureForcePressEndCallback
?
onForcePressEnd
;
/// Called for a tap event with the secondary mouse button.
final
GestureTapCallback
?
onSecondaryTap
;
/// Called for a tap down event with the secondary mouse button.
final
GestureTapDownCallback
?
onSecondaryTapDown
;
/// Called for each distinct tap except for every second tap of a double tap.
/// For example, if the detector was configured with [onTapDown] and
/// [onDoubleTapDown], three quick taps would be recognized as a single tap
...
...
@@ -1419,6 +1486,8 @@ class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetec
()
=>
_TransparentTapGestureRecognizer
(
debugOwner:
this
),
(
_TransparentTapGestureRecognizer
instance
)
{
instance
..
onSecondaryTap
=
widget
.
onSecondaryTap
..
onSecondaryTapDown
=
widget
.
onSecondaryTapDown
..
onTapDown
=
_handleTapDown
..
onTapUp
=
_handleTapUp
..
onTapCancel
=
_handleTapCancel
;
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
24e195d9
...
...
@@ -9,7 +9,7 @@ import 'package:flutter/cupertino.dart';
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
show
DragStartBehavior
,
PointerDeviceKind
;
import
'package:flutter/gestures.dart'
show
DragStartBehavior
,
PointerDeviceKind
,
kSecondaryMouseButton
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../rendering/mock_canvas.dart'
;
...
...
@@ -40,13 +40,15 @@ class MockTextSelectionControls extends TextSelectionControls {
@override
Widget
buildToolbar
(
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
)
{
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
throw
UnimplementedError
();
}
...
...
@@ -209,6 +211,79 @@ void main() {
await
Clipboard
.
setData
(
const
ClipboardData
(
text:
'Clipboard data'
));
});
testWidgets
(
'can use the desktop cut/copy/paste buttons on Mac'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'blah1 blah2'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
ConstrainedBox
(
constraints:
BoxConstraints
.
loose
(
const
Size
(
400
,
200
)),
child:
CupertinoTextField
(
controller:
controller
),
),
),
),
);
// Initially, the menu is not shown and there is no selection.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
-
1
,
extentOffset:
-
1
));
final
Offset
midBlah1
=
textOffsetToPosition
(
tester
,
2
);
// Right clicking shows the menu.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
midBlah1
,
kind:
PointerDeviceKind
.
mouse
,
buttons:
kSecondaryMouseButton
,
);
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
5
));
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
find
.
text
(
'Cut'
),
findsOneWidget
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
// Copy the first word.
await
tester
.
tap
(
find
.
text
(
'Copy'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
'blah1 blah2'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
5
,
extentOffset:
5
));
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
// Paste it at the end.
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
controller
.
text
.
length
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
11
,
extentOffset:
11
,
affinity:
TextAffinity
.
upstream
));
expect
(
find
.
text
(
'Cut'
),
findsNothing
);
expect
(
find
.
text
(
'Copy'
),
findsNothing
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Paste'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
'blah1 blah2blah1'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
16
,
extentOffset:
16
));
// Cut the first word.
await
gesture
.
down
(
midBlah1
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Cut'
),
findsOneWidget
);
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
5
));
await
tester
.
tap
(
find
.
text
(
'Cut'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
' blah2blah1'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
0
));
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}),
skip:
kIsWeb
);
testWidgets
(
'takes available space horizontally and takes intrinsic space vertically no-strut'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -1861,6 +1936,74 @@ void main() {
expect
(
controller
.
value
.
selection
.
extentOffset
,
1
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}));
testWidgets
(
'double clicking a space selects the space on Mac'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
' blah blah'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
-
1
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
-
1
);
// Put the cursor at the end of the field.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textOffsetToPosition
(
tester
,
10
),
pointer:
7
,
kind:
PointerDeviceKind
.
mouse
,
);
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pump
();
await
gesture
.
up
();
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
10
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
10
);
// Double tapping the second space selects it.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
5
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
5
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
5
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
6
);
// Put the cursor at the end of the field.
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
10
));
await
tester
.
pump
();
await
gesture
.
up
();
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
10
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
10
);
// Double tapping the first space selects it.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
0
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
1
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}));
testWidgets
(
'An obscured CupertinoTextField is not selectable when disabled'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/cupertino/text_selection_test.dart
View file @
24e195d9
...
...
@@ -9,7 +9,7 @@ import 'package:flutter/material.dart';
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'../widgets/
text
.dart'
show
textOffsetToPosition
;
import
'../widgets/
editable_text_utils
.dart'
show
textOffsetToPosition
;
class
MockClipboard
{
Object
_clipboardData
=
<
String
,
dynamic
>{
...
...
packages/flutter/test/cupertino/text_selection_toolbar_test.dart
View file @
24e195d9
...
...
@@ -8,7 +8,7 @@ import 'package:flutter/cupertino.dart';
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'../widgets/
text
.dart'
show
textOffsetToPosition
;
import
'../widgets/
editable_text_utils
.dart'
show
textOffsetToPosition
;
// These constants are copied from cupertino/text_selection_toolbar.dart.
const
double
_kArrowScreenPadding
=
26.0
;
...
...
@@ -26,6 +26,7 @@ class _CustomCupertinoTextSelectionControls extends CupertinoTextSelectionContro
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
final
MediaQueryData
mediaQuery
=
MediaQuery
.
of
(
context
);
final
double
anchorX
=
(
selectionMidpoint
.
dx
+
globalEditableRegion
.
left
).
clamp
(
...
...
packages/flutter/test/material/text_field_test.dart
View file @
24e195d9
...
...
@@ -7,6 +7,7 @@ import 'dart:math' as math;
import
'dart:ui'
as
ui
show
window
,
BoxHeightStyle
,
BoxWidthStyle
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
...
...
@@ -14,8 +15,8 @@ import 'package:flutter/services.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
show
DragStartBehavior
,
PointerDeviceKind
;
import
'../widgets/editable_text_utils.dart'
show
findRenderEditable
,
globalize
,
textOffsetToPosition
;
import
'../widgets/semantics_tester.dart'
;
import
'../widgets/text.dart'
show
findRenderEditable
,
globalize
,
textOffsetToPosition
;
import
'feedback_tester.dart'
;
class
MockClipboard
{
...
...
@@ -167,6 +168,78 @@ void main() {
);
}
testWidgets
(
'can use the desktop cut/copy/paste buttons on Mac'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'blah1 blah2'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
,
),
),
),
);
// Initially, the menu is not shown and there is no selection.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
-
1
,
extentOffset:
-
1
));
final
Offset
midBlah1
=
textOffsetToPosition
(
tester
,
2
);
// Right clicking shows the menu.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
midBlah1
,
kind:
PointerDeviceKind
.
mouse
,
buttons:
kSecondaryMouseButton
,
);
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
5
));
expect
(
find
.
text
(
'Cut'
),
findsOneWidget
);
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
// Copy the first word.
await
tester
.
tap
(
find
.
text
(
'Copy'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
'blah1 blah2'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
5
,
extentOffset:
5
));
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
// Paste it at the end.
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
controller
.
text
.
length
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
11
,
extentOffset:
11
,
affinity:
TextAffinity
.
upstream
));
expect
(
find
.
text
(
'Cut'
),
findsNothing
);
expect
(
find
.
text
(
'Copy'
),
findsNothing
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Paste'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
'blah1 blah2blah1'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
16
,
extentOffset:
16
));
// Cut the first word.
await
gesture
.
down
(
midBlah1
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Cut'
),
findsOneWidget
);
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
5
));
await
tester
.
tap
(
find
.
text
(
'Cut'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
' blah2blah1'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
0
));
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}),
skip:
kIsWeb
);
testWidgets
(
'TextField passes onEditingComplete to EditableText'
,
(
WidgetTester
tester
)
async
{
final
VoidCallback
onEditingComplete
=
()
{
};
...
...
@@ -7144,6 +7217,10 @@ void main() {
await
tester
.
longPressAt
(
textfieldStart
+
const
Offset
(
50.0
,
9.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
3
,
affinity:
TextAffinity
.
downstream
),
);
await
tester
.
tapAt
(
textfieldStart
+
const
Offset
(
150.0
,
9.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
...
...
@@ -7161,7 +7238,65 @@ void main() {
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
testWidgets
(
'double click after a click on Mac'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
Center
(
child:
TextField
(
controller:
controller
,
),
),
),
),
);
final
Offset
textFieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
TextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textFieldStart
+
const
Offset
(
50.0
,
9.0
),
pointer:
7
,
kind:
PointerDeviceKind
.
mouse
,
);
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
3
,
affinity:
TextAffinity
.
downstream
),
);
await
gesture
.
down
(
textFieldStart
+
const
Offset
(
150.0
,
9.0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First click moved the cursor to the precise location, not the start of
// the word.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
9
,
affinity:
TextAffinity
.
downstream
),
);
// Double click selection.
await
gesture
.
down
(
textFieldStart
+
const
Offset
(
150.0
,
9.0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
// The text selection toolbar isn't shown on Mac without a right click.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}),
skip:
kIsWeb
);
testWidgets
(
'double tap chains work'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
...
...
@@ -7225,7 +7360,92 @@ void main() {
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
testWidgets
(
'double click chains work'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
Center
(
child:
TextField
(
controller:
controller
,
),
),
),
),
);
final
Offset
textFieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
TextField
));
// First click moves the cursor to the point of the click, not the edge of
// the clicked word.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textFieldStart
+
const
Offset
(
50.0
,
9.0
),
pointer:
7
,
kind:
PointerDeviceKind
.
mouse
,
);
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
3
,
affinity:
TextAffinity
.
downstream
),
);
// Second click selects.
await
gesture
.
down
(
textFieldStart
+
const
Offset
(
50.0
,
9.0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
7
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
// Double tap selecting the same word somewhere else is fine.
await
gesture
.
down
(
textFieldStart
+
const
Offset
(
100.0
,
9.0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First tap moved the cursor.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
6
,
affinity:
TextAffinity
.
downstream
),
);
await
gesture
.
down
(
textFieldStart
+
const
Offset
(
100.0
,
9.0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
7
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
down
(
textFieldStart
+
const
Offset
(
150.0
,
9.0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// First tap moved the cursor.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
9
,
affinity:
TextAffinity
.
downstream
),
);
await
gesture
.
down
(
textFieldStart
+
const
Offset
(
150.0
,
9.0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}),
skip:
kIsWeb
);
testWidgets
(
'double tapping a space selects the previous word on iOS'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
...
...
@@ -7348,7 +7568,78 @@ void main() {
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
0
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
1
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
,
TargetPlatform
.
windows
,
TargetPlatform
.
linux
,
TargetPlatform
.
fuchsia
,
TargetPlatform
.
android
}));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
,
TargetPlatform
.
windows
,
TargetPlatform
.
linux
,
TargetPlatform
.
fuchsia
,
TargetPlatform
.
android
}));
testWidgets
(
'selecting a space selects the space on Mac'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
' blah blah'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
Center
(
child:
TextField
(
controller:
controller
,
),
),
),
),
);
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
-
1
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
-
1
);
// Put the cursor at the end of the field.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textOffsetToPosition
(
tester
,
10
),
pointer:
7
,
kind:
PointerDeviceKind
.
mouse
,
);
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pump
();
await
gesture
.
up
();
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
10
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
10
);
// Double clicking the second space selects it.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
5
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
5
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
5
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
6
);
// Put the cursor at the end of the field.
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
10
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
10
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
10
);
// Double tapping the second space selects it.
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
0
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
value
.
selection
,
isNotNull
);
expect
(
controller
.
value
.
selection
.
baseOffset
,
0
);
expect
(
controller
.
value
.
selection
.
extentOffset
,
1
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}));
testWidgets
(
'force press does not select a word'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
...
...
@@ -7436,7 +7727,7 @@ void main() {
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
}));
testWidgets
(
'tap on non-force-press-supported devices work'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
...
...
packages/flutter/test/material/text_form_field_test.dart
View file @
24e195d9
...
...
@@ -2,14 +2,118 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'../widgets/editable_text_utils.dart'
;
class
MockClipboard
{
Object
_clipboardData
=
<
String
,
dynamic
>{
'text'
:
null
,
};
Future
<
dynamic
>
handleMethodCall
(
MethodCall
methodCall
)
async
{
switch
(
methodCall
.
method
)
{
case
'Clipboard.getData'
:
return
_clipboardData
;
case
'Clipboard.setData'
:
_clipboardData
=
methodCall
.
arguments
as
Object
;
break
;
}
}
}
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
final
MockClipboard
mockClipboard
=
MockClipboard
();
SystemChannels
.
platform
.
setMockMethodCallHandler
(
mockClipboard
.
handleMethodCall
);
setUp
(()
async
{
// Fill the clipboard so that the Paste option is available in the text
// selection menu.
await
Clipboard
.
setData
(
const
ClipboardData
(
text:
'Clipboard data'
));
});
testWidgets
(
'can use the desktop cut/copy/paste buttons on Mac'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'blah1 blah2'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
Center
(
child:
TextFormField
(
controller:
controller
,
),
),
),
),
);
// Initially, the menu is not shown and there is no selection.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
-
1
,
extentOffset:
-
1
));
final
Offset
midBlah1
=
textOffsetToPosition
(
tester
,
2
);
// Right clicking shows the menu.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
midBlah1
,
kind:
PointerDeviceKind
.
mouse
,
buttons:
kSecondaryMouseButton
,
);
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
5
));
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
find
.
text
(
'Cut'
),
findsOneWidget
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
// Copy the first word.
await
tester
.
tap
(
find
.
text
(
'Copy'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
'blah1 blah2'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
5
,
extentOffset:
5
));
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
// Paste it at the end.
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
controller
.
text
.
length
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
11
,
extentOffset:
11
,
affinity:
TextAffinity
.
upstream
));
expect
(
find
.
text
(
'Cut'
),
findsNothing
);
expect
(
find
.
text
(
'Copy'
),
findsNothing
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Paste'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
'blah1 blah2blah1'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
16
,
extentOffset:
16
));
// Cut the first word.
await
gesture
.
down
(
midBlah1
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Cut'
),
findsOneWidget
);
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
5
));
await
tester
.
tap
(
find
.
text
(
'Cut'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
' blah2blah1'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
0
));
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}),
skip:
kIsWeb
);
testWidgets
(
'Passes textAlign to underlying TextField'
,
(
WidgetTester
tester
)
async
{
const
TextAlign
alignment
=
TextAlign
.
center
;
...
...
packages/flutter/test/material/text_selection_test.dart
View file @
24e195d9
...
...
@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'../widgets/
text
.dart'
show
findRenderEditable
,
globalize
,
textOffsetToPosition
;
import
'../widgets/
editable_text_utils
.dart'
show
findRenderEditable
,
globalize
,
textOffsetToPosition
;
class
MockClipboard
{
Object
_clipboardData
=
<
String
,
dynamic
>{
...
...
packages/flutter/test/material/text_selection_toolbar_test.dart
View file @
24e195d9
...
...
@@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'../widgets/
text
.dart'
show
textOffsetToPosition
;
import
'../widgets/
editable_text_utils
.dart'
show
textOffsetToPosition
;
// A custom text selection menu that just displays a single custom button.
class
_CustomMaterialTextSelectionControls
extends
MaterialTextSelectionControls
{
...
...
@@ -24,6 +24,7 @@ class _CustomMaterialTextSelectionControls extends MaterialTextSelectionControls
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
final
TextSelectionPoint
startTextSelectionPoint
=
endpoints
[
0
];
final
TextSelectionPoint
endTextSelectionPoint
=
endpoints
.
length
>
1
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
24e195d9
...
...
@@ -6984,7 +6984,7 @@ class MockTextFormatter extends TextInputFormatter {
class
MockTextSelectionControls
extends
Fake
implements
TextSelectionControls
{
@override
Widget
buildToolbar
(
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
)
{
Widget
buildToolbar
(
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
)
{
return
Container
();
}
...
...
packages/flutter/test/widgets/editable_text_utils.dart
View file @
24e195d9
...
...
@@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
import
'package:flutter/material.dart'
;
import
'package:flutter/widgets.dart'
;
// Returns the first RenderEditable.
RenderEditable
findRenderEditable
(
WidgetTester
tester
)
{
final
RenderObject
root
=
tester
.
renderObject
(
find
.
byType
(
EditableText
));
expect
(
root
,
isNotNull
);
...
...
@@ -23,3 +24,24 @@ RenderEditable findRenderEditable(WidgetTester tester) {
expect
(
renderEditable
,
isNotNull
);
return
renderEditable
;
}
List
<
TextSelectionPoint
>
globalize
(
Iterable
<
TextSelectionPoint
>
points
,
RenderBox
box
)
{
return
points
.
map
<
TextSelectionPoint
>((
TextSelectionPoint
point
)
{
return
TextSelectionPoint
(
box
.
localToGlobal
(
point
.
point
),
point
.
direction
,
);
}).
toList
();
}
Offset
textOffsetToPosition
(
WidgetTester
tester
,
int
offset
)
{
final
RenderEditable
renderEditable
=
findRenderEditable
(
tester
);
final
List
<
TextSelectionPoint
>
endpoints
=
globalize
(
renderEditable
.
getEndpointsForSelection
(
TextSelection
.
collapsed
(
offset:
offset
),
),
renderEditable
,
);
expect
(
endpoints
.
length
,
1
);
return
endpoints
[
0
].
point
+
const
Offset
(
0.0
,
-
2.0
);
}
packages/flutter/test/widgets/selectable_text_test.dart
View file @
24e195d9
...
...
@@ -11,8 +11,8 @@ import 'package:flutter/services.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'../widgets/editable_text_utils.dart'
show
textOffsetToPosition
;
import
'../widgets/semantics_tester.dart'
;
import
'../widgets/text.dart'
show
textOffsetToPosition
;
class
MockClipboard
{
dynamic
_clipboardData
=
<
String
,
dynamic
>{
...
...
@@ -161,8 +161,11 @@ void main() {
}).
toList
();
}
setUp
(()
{
setUp
(()
async
{
debugResetSemanticsIdCounter
();
// Fill the clipboard so that the Paste option is available in the text
// selection menu.
await
Clipboard
.
setData
(
const
ClipboardData
(
text:
'Clipboard data'
));
});
Widget
selectableTextBuilder
({
...
...
@@ -180,6 +183,80 @@ void main() {
);
}
testWidgets
(
'can use the desktop cut/copy/paste buttons on Mac'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'blah1 blah2'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
Center
(
child:
TextFormField
(
controller:
controller
,
),
),
),
),
);
// Initially, the menu is not shown and there is no selection.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
-
1
,
extentOffset:
-
1
));
final
Offset
midBlah1
=
textOffsetToPosition
(
tester
,
2
);
// Right clicking shows the menu.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
midBlah1
,
kind:
PointerDeviceKind
.
mouse
,
buttons:
kSecondaryMouseButton
,
);
addTearDown
(
gesture
.
removePointer
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
5
));
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
find
.
text
(
'Cut'
),
findsOneWidget
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
// Copy the first word.
await
tester
.
tap
(
find
.
text
(
'Copy'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
'blah1 blah2'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
5
,
extentOffset:
5
));
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
// Paste it at the end.
await
gesture
.
down
(
textOffsetToPosition
(
tester
,
controller
.
text
.
length
));
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
11
,
extentOffset:
11
,
affinity:
TextAffinity
.
upstream
));
expect
(
find
.
text
(
'Cut'
),
findsNothing
);
expect
(
find
.
text
(
'Copy'
),
findsNothing
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Paste'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
'blah1 blah2blah1'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
16
,
extentOffset:
16
));
// Cut the first word.
await
gesture
.
down
(
midBlah1
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Cut'
),
findsOneWidget
);
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
5
));
await
tester
.
tap
(
find
.
text
(
'Cut'
));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
text
,
' blah2blah1'
);
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
0
,
extentOffset:
0
));
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
macOS
}),
skip:
kIsWeb
);
testWidgets
(
'has expected defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
boilerplate
(
...
...
@@ -3504,7 +3581,7 @@ void main() {
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
1
));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
}));
testWidgets
(
'tap on non-force-press-supported devices work'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
...
...
packages/flutter/test/widgets/text.dart
deleted
100644 → 0
View file @
c66596e5
// 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_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
// Returns the first RenderEditable.
RenderEditable
findRenderEditable
(
WidgetTester
tester
)
{
final
RenderObject
root
=
tester
.
renderObject
(
find
.
byType
(
EditableText
));
expect
(
root
,
isNotNull
);
late
RenderEditable
renderEditable
;
void
recursiveFinder
(
RenderObject
child
)
{
if
(
child
is
RenderEditable
)
{
renderEditable
=
child
;
return
;
}
child
.
visitChildren
(
recursiveFinder
);
}
root
.
visitChildren
(
recursiveFinder
);
expect
(
renderEditable
,
isNotNull
);
return
renderEditable
;
}
List
<
TextSelectionPoint
>
globalize
(
Iterable
<
TextSelectionPoint
>
points
,
RenderBox
box
)
{
return
points
.
map
<
TextSelectionPoint
>((
TextSelectionPoint
point
)
{
return
TextSelectionPoint
(
box
.
localToGlobal
(
point
.
point
),
point
.
direction
,
);
}).
toList
();
}
Offset
textOffsetToPosition
(
WidgetTester
tester
,
int
offset
)
{
final
RenderEditable
renderEditable
=
findRenderEditable
(
tester
);
final
List
<
TextSelectionPoint
>
endpoints
=
globalize
(
renderEditable
.
getEndpointsForSelection
(
TextSelection
.
collapsed
(
offset:
offset
),
),
renderEditable
,
);
expect
(
endpoints
.
length
,
1
);
return
endpoints
[
0
].
point
+
const
Offset
(
0.0
,
-
2.0
);
}
packages/flutter/test/widgets/text_selection_test.dart
View file @
24e195d9
...
...
@@ -672,7 +672,7 @@ void main() {
expect
(
hitRect
.
size
.
width
,
lessThan
(
textFieldRect
.
size
.
width
));
expect
(
hitRect
.
size
.
height
,
lessThan
(
textFieldRect
.
size
.
height
));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
}));
group
(
'ClipboardStatusNotifier'
,
()
{
group
(
'when Clipboard fails'
,
()
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment