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
14414f35
Unverified
Commit
14414f35
authored
Jun 06, 2019
by
Gary Qian
Committed by
GitHub
Jun 06, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reland "Text inline widgets, TextSpan rework" (#33946)
parent
881cdfde
Changes
27
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
2444 additions
and
241 deletions
+2444
-241
goldens.version
bin/internal/goldens.version
+1
-1
main.dart
dev/benchmarks/complex_layout/lib/main.dart
+2
-2
text.dart
dev/manual_tests/lib/text.dart
+1
-1
painting.dart
packages/flutter/lib/painting.dart
+3
-1
time_picker.dart
packages/flutter/lib/src/material/time_picker.dart
+2
-1
inline_span.dart
packages/flutter/lib/src/painting/inline_span.dart
+249
-0
placeholder_span.dart
packages/flutter/lib/src/painting/placeholder_span.dart
+85
-0
text_painter.dart
packages/flutter/lib/src/painting/text_painter.dart
+134
-13
text_span.dart
packages/flutter/lib/src/painting/text_span.dart
+116
-117
box.dart
packages/flutter/lib/src/rendering/box.dart
+4
-1
debug_overflow_indicator.dart
...s/flutter/lib/src/rendering/debug_overflow_indicator.dart
+2
-2
paragraph.dart
packages/flutter/lib/src/rendering/paragraph.dart
+330
-65
basic.dart
packages/flutter/lib/src/widgets/basic.dart
+20
-4
text.dart
packages/flutter/lib/src/widgets/text.dart
+10
-3
widget_inspector.dart
packages/flutter/lib/src/widgets/widget_inspector.dart
+2
-1
widget_span.dart
packages/flutter/lib/src/widgets/widget_span.dart
+198
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
text_painter_rtl_test.dart
packages/flutter/test/painting/text_painter_rtl_test.dart
+8
-4
text_painter_test.dart
packages/flutter/test/painting/text_painter_test.dart
+95
-0
text_span_test.dart
packages/flutter/test/painting/text_span_test.dart
+132
-7
paragraph_test.dart
packages/flutter/test/rendering/paragraph_test.dart
+90
-0
backdrop_filter_test.dart
packages/flutter/test/widgets/backdrop_filter_test.dart
+1
-0
basic_test.dart
packages/flutter/test/widgets/basic_test.dart
+0
-1
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+3
-2
text_golden_test.dart
packages/flutter/test/widgets/text_golden_test.dart
+815
-12
text_test.dart
packages/flutter/test/widgets/text_test.dart
+136
-2
widget_inspector_test.dart
packages/flutter/test/widgets/widget_inspector_test.dart
+4
-1
No files found.
bin/internal/goldens.version
View file @
14414f35
041efaf483a1cd011f4b4f6dcd04e0d5cad9436e
eb9c1d66709a1f8a0291076865fe387ceed96dca
dev/benchmarks/complex_layout/lib/main.dart
View file @
14414f35
...
@@ -438,8 +438,8 @@ class ItemImageBox extends StatelessWidget {
...
@@ -438,8 +438,8 @@ class ItemImageBox extends StatelessWidget {
borderRadius:
BorderRadius
.
circular
(
2.0
),
borderRadius:
BorderRadius
.
circular
(
2.0
),
),
),
padding:
const
EdgeInsets
.
all
(
4.0
),
padding:
const
EdgeInsets
.
all
(
4.0
),
child:
const
RichText
(
child:
RichText
(
text:
TextSpan
(
text:
const
TextSpan
(
style:
TextStyle
(
color:
Colors
.
white
),
style:
TextStyle
(
color:
Colors
.
white
),
children:
<
TextSpan
>[
children:
<
TextSpan
>[
TextSpan
(
TextSpan
(
...
...
dev/manual_tests/lib/text.dart
View file @
14414f35
...
@@ -145,7 +145,7 @@ class _FuzzerState extends State<Fuzzer> with SingleTickerProviderStateMixin {
...
@@ -145,7 +145,7 @@ class _FuzzerState extends State<Fuzzer> with SingleTickerProviderStateMixin {
return
TextSpan
(
return
TextSpan
(
text:
_fiddleWithText
(
node
.
text
),
text:
_fiddleWithText
(
node
.
text
),
style:
_fiddleWithStyle
(
node
.
style
),
style:
_fiddleWithStyle
(
node
.
style
),
children:
_fiddleWithChildren
(
node
.
children
?.
map
((
TextSpan
child
)
=>
_fiddleWith
(
child
))?.
toList
()
??
<
Text
Span
>[]),
children:
_fiddleWithChildren
(
node
.
children
?.
map
((
InlineSpan
child
)
=>
_fiddleWith
(
child
))?.
toList
()
??
<
Inline
Span
>[]),
);
);
}
}
...
...
packages/flutter/lib/painting.dart
View file @
14414f35
...
@@ -17,7 +17,7 @@
...
@@ -17,7 +17,7 @@
/// painting boxes.
/// painting boxes.
library
painting
;
library
painting
;
export
'dart:ui'
show
Shadow
;
export
'dart:ui'
show
Shadow
,
PlaceholderAlignment
;
export
'src/painting/alignment.dart'
;
export
'src/painting/alignment.dart'
;
export
'src/painting/basic_types.dart'
;
export
'src/painting/basic_types.dart'
;
...
@@ -46,9 +46,11 @@ export 'src/painting/image_decoder.dart';
...
@@ -46,9 +46,11 @@ export 'src/painting/image_decoder.dart';
export
'src/painting/image_provider.dart'
;
export
'src/painting/image_provider.dart'
;
export
'src/painting/image_resolution.dart'
;
export
'src/painting/image_resolution.dart'
;
export
'src/painting/image_stream.dart'
;
export
'src/painting/image_stream.dart'
;
export
'src/painting/inline_span.dart'
;
export
'src/painting/matrix_utils.dart'
;
export
'src/painting/matrix_utils.dart'
;
export
'src/painting/notched_shapes.dart'
;
export
'src/painting/notched_shapes.dart'
;
export
'src/painting/paint_utilities.dart'
;
export
'src/painting/paint_utilities.dart'
;
export
'src/painting/placeholder_span.dart'
;
export
'src/painting/rounded_rectangle_border.dart'
;
export
'src/painting/rounded_rectangle_border.dart'
;
export
'src/painting/shader_warm_up.dart'
;
export
'src/painting/shader_warm_up.dart'
;
export
'src/painting/shape_decoration.dart'
;
export
'src/painting/shape_decoration.dart'
;
...
...
packages/flutter/lib/src/material/time_picker.dart
View file @
14414f35
...
@@ -993,6 +993,7 @@ class _DialPainter extends CustomPainter {
...
@@ -993,6 +993,7 @@ class _DialPainter extends CustomPainter {
final
double
width
=
labelPainter
.
width
*
_semanticNodeSizeScale
;
final
double
width
=
labelPainter
.
width
*
_semanticNodeSizeScale
;
final
double
height
=
labelPainter
.
height
*
_semanticNodeSizeScale
;
final
double
height
=
labelPainter
.
height
*
_semanticNodeSizeScale
;
final
Offset
nodeOffset
=
getOffsetForTheta
(
labelTheta
,
ring
)
+
Offset
(-
width
/
2.0
,
-
height
/
2.0
);
final
Offset
nodeOffset
=
getOffsetForTheta
(
labelTheta
,
ring
)
+
Offset
(-
width
/
2.0
,
-
height
/
2.0
);
final
TextSpan
textSpan
=
labelPainter
.
text
;
final
CustomPainterSemantics
node
=
CustomPainterSemantics
(
final
CustomPainterSemantics
node
=
CustomPainterSemantics
(
rect:
Rect
.
fromLTRB
(
rect:
Rect
.
fromLTRB
(
nodeOffset
.
dx
-
24.0
+
width
/
2
,
nodeOffset
.
dx
-
24.0
+
width
/
2
,
...
@@ -1003,7 +1004,7 @@ class _DialPainter extends CustomPainter {
...
@@ -1003,7 +1004,7 @@ class _DialPainter extends CustomPainter {
properties:
SemanticsProperties
(
properties:
SemanticsProperties
(
sortKey:
OrdinalSortKey
(
i
.
toDouble
()
+
ordinalOffset
),
sortKey:
OrdinalSortKey
(
i
.
toDouble
()
+
ordinalOffset
),
selected:
label
.
value
==
selectedValue
,
selected:
label
.
value
==
selectedValue
,
value:
labelPainter
.
text
.
text
,
value:
textSpan
?
.
text
,
textDirection:
textDirection
,
textDirection:
textDirection
,
onTap:
label
.
onTap
,
onTap:
label
.
onTap
,
),
),
...
...
packages/flutter/lib/src/painting/inline_span.dart
0 → 100644
View file @
14414f35
// Copyright 2015 The Chromium 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:ui'
as
ui
show
ParagraphBuilder
;
import
'package:flutter/foundation.dart'
;
import
'basic_types.dart'
;
import
'text_painter.dart'
;
import
'text_style.dart'
;
/// Mutable wrapper of an integer that can be passed by reference to track a
/// value across a recursive stack.
class
Accumulator
{
/// [Accumulator] may be initialized with a specified value, otherwise, it will
/// initialize to zero.
Accumulator
([
this
.
_value
=
0
]);
/// The integer stored in this [Accumulator].
int
get
value
=>
_value
;
int
_value
;
/// Increases the [value] by the `addend`.
void
increment
(
int
addend
)
{
assert
(
addend
>=
0
);
_value
+=
addend
;
}
}
/// Called on each span as [InlineSpan.visitChildren] walks the [InlineSpan] tree.
///
/// Returns true when the walk should continue, and false to stop visiting further
/// [InlineSpan]s.
typedef
InlineSpanVisitor
=
bool
Function
(
InlineSpan
span
);
/// An immutable span of inline content which forms part of a paragraph.
///
/// * The subclass [TextSpan] specifies text and may contain child [InlineSpan]s.
/// * The subclass [PlaceholderSpan] represents a placeholder that may be
/// filled with non-text content. [PlaceholderSpan] itself defines a
/// [ui.PlaceholderAlignemnt] and a [TextBaseline]. To be useful,
/// [PlaceholderSpan] must be extended to define content. An instance of
/// this is the [WidgetSpan] class in the widgets library.
/// * The subclass [WidgetSpan] specifies embedded inline widgets.
///
/// {@tool sample}
///
/// This example shows a tree of [InlineSpan]s that make a query asking for a
/// name with a [TextField] embedded inline.
///
/// ```dart
/// Text.rich(
/// TextSpan(
/// text: 'My name is ',
/// style: TextStyle(color: Colors.black),
/// children: <InlineSpan>[
/// WidgetSpan(
/// alignment: PlaceholderAlignment.baseline,
/// baseline: TextBaseline.alphabetic,
/// child: ConstrainedBox(
/// constraints: BoxConstraints(maxWidth: 100),
/// child: TextField(),
/// )
/// ),
/// TextSpan(
/// text: '.',
/// ),
/// ],
/// ),
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Text], a widget for showing uniformly-styled text.
/// * [RichText], a widget for finer control of text rendering.
/// * [TextPainter], a class for painting [InlineSpan] objects on a [Canvas].
@immutable
abstract
class
InlineSpan
extends
DiagnosticableTree
{
/// Creates an [InlineSpan] with the given values.
const
InlineSpan
({
this
.
style
,
});
/// The [TextStyle] to apply to this span.
///
/// The [style] is also applied to any child spans when this is an instance
/// of [TextSpan].
final
TextStyle
style
;
/// Apply the properties of this object to the given [ParagraphBuilder], from
/// which a [Paragraph] can be obtained.
///
/// The `textScaleFactor` parameter specifies a scale that the text and
/// placeholders will be scaled by. The scaling is performed before layout,
/// so the text will be laid out with the scaled glyphs and placeholders.
///
/// The `dimensions` parameter specifies the sizes of the placeholders.
/// Each [PlaceholderSpan] must be paired with a [PlaceholderDimensions]
/// in the same order as defined in the [InlineSpan] tree.
///
/// [Paragraph] objects can be drawn on [Canvas] objects.
void
build
(
ui
.
ParagraphBuilder
builder
,
{
double
textScaleFactor
=
1.0
,
List
<
PlaceholderDimensions
>
dimensions
});
/// Walks this [InlineSpan] and any descendants in pre-order and calls `visitor`
/// for each span that has content.
///
/// When `visitor` returns true, the walk will continue. When `visitor` returns
/// false, then the walk will end.
bool
visitChildren
(
InlineSpanVisitor
visitor
);
/// Returns the text span that contains the given position in the text.
InlineSpan
getSpanForPosition
(
TextPosition
position
)
{
assert
(
debugAssertIsValid
());
final
Accumulator
offset
=
Accumulator
();
InlineSpan
result
;
visitChildren
((
InlineSpan
span
)
{
result
=
span
.
getSpanForPositionVisitor
(
position
,
offset
);
return
result
==
null
;
});
return
result
;
}
/// Performs the check at each [InlineSpan] for if the `position` falls within the range
/// of the span and returns the span if it does.
///
/// The `offset` parameter tracks the current index offset in the text buffer formed
/// if the contents of the [InlineSpan] tree were concatenated together starting
/// from the root [InlineSpan].
///
/// This method should not be directly called. Use [getSpanForPosition] instead.
@protected
InlineSpan
getSpanForPositionVisitor
(
TextPosition
position
,
Accumulator
offset
);
/// Flattens the [InlineSpan] tree into a single string.
///
/// Styles are not honored in this process. If `includeSemanticsLabels` is
/// true, then the text returned will include the [TextSpan.semanticsLabel]s
/// instead of the text contents for [TextSpan]s.
///
/// When `includePlaceholders` is true, [PlaceholderSpan]s in the tree will be
/// represented as a 0xFFFC 'object replacement character'.
String
toPlainText
({
bool
includeSemanticsLabels
=
true
,
bool
includePlaceholders
=
true
})
{
final
StringBuffer
buffer
=
StringBuffer
();
computeToPlainText
(
buffer
,
includeSemanticsLabels:
includeSemanticsLabels
,
includePlaceholders:
includePlaceholders
);
return
buffer
.
toString
();
}
/// Walks the [InlineSpan] tree and writes the plain text representation to `buffer`.
///
/// This method should not be directly called. Use [toPlainText] instead.
///
/// Styles are not honored in this process. If `includeSemanticsLabels` is
/// true, then the text returned will include the [TextSpan.semanticsLabel]s
/// instead of the text contents for [TextSpan]s.
///
/// When `includePlaceholders` is true, [PlaceholderSpan]s in the tree will be
/// represented as a 0xFFFC 'object replacement character'.
///
/// The plain-text representation of this [InlineSpan] is written into the `buffer`.
/// This method will then recursively call [computeToPlainText] on its childen
/// [InlineSpan]s if available.
@protected
void
computeToPlainText
(
StringBuffer
buffer
,
{
bool
includeSemanticsLabels
=
true
,
bool
includePlaceholders
=
true
});
/// Returns the UTF-16 code unit at the given `index` in the flattened string.
///
/// This only accounts for the [TextSpan.text] values and ignores [PlaceholderSpans].
///
/// Returns null if the `index` is out of bounds.
int
codeUnitAt
(
int
index
)
{
if
(
index
<
0
)
return
null
;
final
Accumulator
offset
=
Accumulator
();
int
result
;
visitChildren
((
InlineSpan
span
)
{
result
=
span
.
codeUnitAtVisitor
(
index
,
offset
);
return
result
==
null
;
});
return
result
;
}
/// Performs the check at each [InlineSpan] for if the `index` falls within the range
/// of the span and returns the corresponding code unit. Returns null otherwise.
///
/// The `offset` parameter tracks the current index offset in the text buffer formed
/// if the contents of the [InlineSpan] tree were concatenated together starting
/// from the root [InlineSpan].
///
/// This method should not be directly called. Use [codeUnitAt] instead.
@protected
int
codeUnitAtVisitor
(
int
index
,
Accumulator
offset
);
/// Populates the `semanticsOffsets` and `semanticsElements` with the appropriate data
/// to be able to construct a [SemanticsNode].
///
/// If applicable, the beginning and end text offset are added to [semanticsOffsets].
/// [PlaceholderSpan]s have a text length of 1, which corresponds to the object
/// replacement character (0xFFFC) that is inserted to represent it.
///
/// Any [GestureRecognizer]s are added to `semanticsElements`. Null is added to
/// `semanticsElements` for [PlaceholderSpan]s.
void
describeSemantics
(
Accumulator
offset
,
List
<
int
>
semanticsOffsets
,
List
<
dynamic
>
semanticsElements
);
/// In checked mode, throws an exception if the object is not in a
/// valid configuration. Otherwise, returns true.
///
/// This is intended to be used as follows:
///
/// ```dart
/// assert(myInlineSpan.debugAssertIsValid());
/// ```
bool
debugAssertIsValid
()
=>
true
;
/// Describe the difference between this span and another, in terms of
/// how much damage it will make to the rendering. The comparison is deep.
///
/// Comparing [InlineSpan] objects of different types, for example, comparing
/// a [TextSpan] to a [WidgetSpan], always results in [RenderComparison.layout].
///
/// See also:
///
/// * [TextStyle.compareTo], which does the same thing for [TextStyle]s.
RenderComparison
compareTo
(
InlineSpan
other
);
@override
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
final
InlineSpan
typedOther
=
other
;
return
typedOther
.
style
==
style
;
}
@override
int
get
hashCode
=>
style
.
hashCode
;
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
defaultDiagnosticsTreeStyle
=
DiagnosticsTreeStyle
.
whitespace
;
if
(
style
!=
null
)
{
style
.
debugFillProperties
(
properties
);
}
}
}
packages/flutter/lib/src/painting/placeholder_span.dart
0 → 100644
View file @
14414f35
// Copyright 2015 The Chromium 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:ui'
as
ui
show
PlaceholderAlignment
;
import
'package:flutter/foundation.dart'
;
import
'basic_types.dart'
;
import
'inline_span.dart'
;
import
'text_painter.dart'
;
import
'text_span.dart'
;
import
'text_style.dart'
;
/// An immutable placeholder that is embedded inline within text.
///
/// [PlaceholderSpan] represents a placeholder that acts as a stand-in for other
/// content. A [PlaceholderSpan] by itself does not contain useful
/// information to change a [TextSpan]. Instead, this class must be extended
/// to define contents.
///
/// [WidgetSpan] from the widgets library extends [PlaceholderSpan] and may be
/// used instead to specify a widget as the contents of the placeholder.
///
/// See also:
///
/// * [WidgetSpan], a leaf node that represents an embedded inline widget.
/// * [TextSpan], a node that represents text in a [TextSpan] tree.
/// * [Text], a widget for showing uniformly-styled text.
/// * [RichText], a widget for finer control of text rendering.
/// * [TextPainter], a class for painting [TextSpan] objects on a [Canvas].
abstract
class
PlaceholderSpan
extends
InlineSpan
{
/// Creates a [PlaceholderSpan] with the given values.
///
/// A [TextStyle] may be provided with the [style] property, but only the
/// decoration, foreground, background, and spacing options will be used.
const
PlaceholderSpan
({
this
.
alignment
=
ui
.
PlaceholderAlignment
.
bottom
,
this
.
baseline
,
TextStyle
style
,
})
:
super
(
style:
style
,);
/// How the placeholder aligns vertically with the text.
///
/// See [ui.PlaceholderAlignment] for details on each mode.
final
ui
.
PlaceholderAlignment
alignment
;
/// The [TextBaseline] to align against when using [ui.PlaceholderAlignment.baseline],
/// [ui.PlaceholderAlignment.aboveBaseline], and [ui.PlaceholderAlignment.belowBaseline].
///
/// This is ignored when using other alignment modes.
final
TextBaseline
baseline
;
/// [PlaceholderSpan]s are flattened to a `0xFFFC` object replacement character in the
/// plain text representation when `includePlaceholders` is true.
@override
void
computeToPlainText
(
StringBuffer
buffer
,
{
bool
includeSemanticsLabels
=
true
,
bool
includePlaceholders
=
true
})
{
if
(
includePlaceholders
)
{
buffer
.
write
(
'
\
uFFFC'
);
}
}
/// Populates the `semanticsOffsets` and `semanticsElements` with the appropriate data
/// to be able to construct a [SemanticsNode].
///
/// [PlaceholderSpan]s have a text length of 1, which corresponds to the object
/// replacement character (0xFFFC) that is inserted to represent it.
///
/// Null is added to `semanticsElements` for [PlaceholderSpan]s.
@override
void
describeSemantics
(
Accumulator
offset
,
List
<
int
>
semanticsOffsets
,
List
<
dynamic
>
semanticsElements
)
{
semanticsOffsets
.
add
(
offset
.
value
);
semanticsOffsets
.
add
(
offset
.
value
+
1
);
semanticsElements
.
add
(
null
);
// null indicates this is a placeholder.
offset
.
increment
(
1
);
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
EnumProperty
<
ui
.
PlaceholderAlignment
>(
'alignment'
,
alignment
,
defaultValue:
null
));
properties
.
add
(
EnumProperty
<
TextBaseline
>(
'baseline'
,
baseline
,
defaultValue:
null
));
}
}
packages/flutter/lib/src/painting/text_painter.dart
View file @
14414f35
...
@@ -3,18 +3,82 @@
...
@@ -3,18 +3,82 @@
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:math'
show
min
,
max
;
import
'dart:math'
show
min
,
max
;
import
'dart:ui'
as
ui
show
Paragraph
,
ParagraphBuilder
,
ParagraphConstraints
,
ParagraphStyle
;
import
'dart:ui'
as
ui
show
Paragraph
,
ParagraphBuilder
,
ParagraphConstraints
,
ParagraphStyle
,
PlaceholderAlignment
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'basic_types.dart'
;
import
'basic_types.dart'
;
import
'inline_span.dart'
;
import
'placeholder_span.dart'
;
import
'strut_style.dart'
;
import
'strut_style.dart'
;
import
'text_span.dart'
;
import
'text_span.dart'
;
export
'package:flutter/services.dart'
show
TextRange
,
TextSelection
;
export
'package:flutter/services.dart'
show
TextRange
,
TextSelection
;
/// Holds the [Size] and baseline required to represent the dimensions of
/// a placeholder in text.
///
/// Placeholders specify an empty space in the text layout, which is used
/// to later render arbitrary inline widgets into defined by a [WidgetSpan].
///
/// The [size] and [alignment] properties are required and cannot be null.
///
/// See also:
///
/// * [WidgetSpan], a subclass of [InlineSpan] and [PlaceholderSpan] that
/// represents an inline widget embedded within text. The space this
/// widget takes is indicated by a placeholder.
/// * [RichText], a text widget that supports text inline widgets.
@immutable
class
PlaceholderDimensions
{
/// Constructs a [PlaceholderDimensions] with the specified parameters.
///
/// The `size` and `alignment` are required as a placeholder's dimensions
/// require at least `size` and `alignment` to be fully defined.
const
PlaceholderDimensions
({
@required
this
.
size
,
@required
this
.
alignment
,
this
.
baseline
,
this
.
baselineOffset
,
})
:
assert
(
size
!=
null
),
assert
(
alignment
!=
null
);
/// Width and height dimensions of the placeholder.
final
Size
size
;
/// How to align the placeholder with the text.
///
/// See also:
///
/// * [baseline], the baseline to align to when using
/// [ui.PlaceholderAlignment.baseline],
/// [ui.PlaceholderAlignment.aboveBaseline],
/// or [ui.PlaceholderAlignment.underBaseline].
/// * [baselineOffset], the distance of the alphabetic baseline from the upper
/// edge of the placeholder.
final
ui
.
PlaceholderAlignment
alignment
;
/// Distance of the [baseline] from the upper edge of the placeholder.
///
/// Only used when [alignment] is [ui.PlaceholderAlignment.baseline].
final
double
baselineOffset
;
/// The [TextBaseline] to align to. Used with:
///
/// * [ui.PlaceholderAlignment.baseline]
/// * [ui.PlaceholderAlignment.aboveBaseline]
/// * [ui.PlaceholderAlignment.underBaseline]
/// * [ui.PlaceholderAlignment.middle]
final
TextBaseline
baseline
;
@override
String
toString
()
{
return
'PlaceholderDimensions(
$size
,
$baseline
)'
;
}
}
/// The different ways of considering the width of one or more lines of text.
/// The different ways of considering the width of one or more lines of text.
///
///
/// See [Text.widthType].
/// See [Text.widthType].
...
@@ -30,6 +94,9 @@ enum TextWidthBasis {
...
@@ -30,6 +94,9 @@ enum TextWidthBasis {
longestLine
,
longestLine
,
}
}
/// This is used to cache and pass the computed metrics regarding the
/// caret's size and position. This is preferred due to the expensive
/// nature of the calculation.
class
_CaretMetrics
{
class
_CaretMetrics
{
const
_CaretMetrics
({
this
.
offset
,
this
.
fullHeight
});
const
_CaretMetrics
({
this
.
offset
,
this
.
fullHeight
});
/// The offset of the top left corner of the caret from the top left
/// The offset of the top left corner of the caret from the top left
...
@@ -67,7 +134,7 @@ class TextPainter {
...
@@ -67,7 +134,7 @@ class TextPainter {
///
///
/// The [maxLines] property, if non-null, must be greater than zero.
/// The [maxLines] property, if non-null, must be greater than zero.
TextPainter
({
TextPainter
({
Text
Span
text
,
Inline
Span
text
,
TextAlign
textAlign
=
TextAlign
.
start
,
TextAlign
textAlign
=
TextAlign
.
start
,
TextDirection
textDirection
,
TextDirection
textDirection
,
double
textScaleFactor
=
1.0
,
double
textScaleFactor
=
1.0
,
...
@@ -99,9 +166,9 @@ class TextPainter {
...
@@ -99,9 +166,9 @@ class TextPainter {
/// After this is set, you must call [layout] before the next call to [paint].
/// After this is set, you must call [layout] before the next call to [paint].
///
///
/// This and [textDirection] must be non-null before you call [layout].
/// This and [textDirection] must be non-null before you call [layout].
Text
Span
get
text
=>
_text
;
Inline
Span
get
text
=>
_text
;
Text
Span
_text
;
Inline
Span
_text
;
set
text
(
Text
Span
value
)
{
set
text
(
Inline
Span
value
)
{
assert
(
value
==
null
||
value
.
debugAssertIsValid
());
assert
(
value
==
null
||
value
.
debugAssertIsValid
());
if
(
_text
==
value
)
if
(
_text
==
value
)
return
;
return
;
...
@@ -266,6 +333,53 @@ class TextPainter {
...
@@ -266,6 +333,53 @@ class TextPainter {
ui
.
Paragraph
_layoutTemplate
;
ui
.
Paragraph
_layoutTemplate
;
/// An ordered list of [TextBox]es that bound the positions of the placeholders
/// in the paragraph.
///
/// Each box corresponds to a [PlaceholderSpan] in the order they were defined
/// in the [InlineSpan] tree.
List
<
TextBox
>
get
inlinePlaceholderBoxes
=>
_inlinePlaceholderBoxes
;
List
<
TextBox
>
_inlinePlaceholderBoxes
;
/// An ordered list of scales for each placeholder in the paragraph.
///
/// The scale is used as a multiplier on the height, width and baselineOffset of
/// the placeholder. Scale is primarily used to handle accessibility scaling.
///
/// Each scale corresponds to a [PlaceholderSpan] in the order they were defined
/// in the [InlineSpan] tree.
List
<
double
>
get
inlinePlaceholderScales
=>
_inlinePlaceholderScales
;
List
<
double
>
_inlinePlaceholderScales
;
/// Sets the dimensions of each placeholder in [text].
///
/// The number of [PlaceholderDimensions] provided should be the same as the
/// number of [PlaceholderSpan]s in text. Passing in an empty or null `value`
/// will do nothing.
///
/// If [layout] is attempted without setting the placeholder dimensions, the
/// placeholders will be ignored in the text layout and no valid
/// [inlinePlaceholderBoxes] will be returned.
void
setPlaceholderDimensions
(
List
<
PlaceholderDimensions
>
value
)
{
if
(
value
==
null
||
value
.
isEmpty
||
listEquals
(
value
,
_placeholderDimensions
))
{
return
;
}
assert
(()
{
int
placeholderCount
=
0
;
text
.
visitChildren
((
InlineSpan
span
)
{
if
(
span
is
PlaceholderSpan
)
{
placeholderCount
+=
1
;
}
return
true
;
});
return
placeholderCount
;
}()
==
value
.
length
);
_placeholderDimensions
=
value
;
_needsLayout
=
true
;
_paragraph
=
null
;
}
List
<
PlaceholderDimensions
>
_placeholderDimensions
;
ui
.
ParagraphStyle
_createParagraphStyle
([
TextDirection
defaultTextDirection
])
{
ui
.
ParagraphStyle
_createParagraphStyle
([
TextDirection
defaultTextDirection
])
{
// The defaultTextDirection argument is used for preferredLineHeight in case
// The defaultTextDirection argument is used for preferredLineHeight in case
// textDirection hasn't yet been set.
// textDirection hasn't yet been set.
...
@@ -419,7 +533,8 @@ class TextPainter {
...
@@ -419,7 +533,8 @@ class TextPainter {
_needsLayout
=
false
;
_needsLayout
=
false
;
if
(
_paragraph
==
null
)
{
if
(
_paragraph
==
null
)
{
final
ui
.
ParagraphBuilder
builder
=
ui
.
ParagraphBuilder
(
_createParagraphStyle
());
final
ui
.
ParagraphBuilder
builder
=
ui
.
ParagraphBuilder
(
_createParagraphStyle
());
_text
.
build
(
builder
,
textScaleFactor:
textScaleFactor
);
_text
.
build
(
builder
,
textScaleFactor:
textScaleFactor
,
dimensions:
_placeholderDimensions
);
_inlinePlaceholderScales
=
builder
.
placeholderScales
;
_paragraph
=
builder
.
build
();
_paragraph
=
builder
.
build
();
}
}
_lastMinWidth
=
minWidth
;
_lastMinWidth
=
minWidth
;
...
@@ -427,10 +542,12 @@ class TextPainter {
...
@@ -427,10 +542,12 @@ class TextPainter {
_paragraph
.
layout
(
ui
.
ParagraphConstraints
(
width:
maxWidth
));
_paragraph
.
layout
(
ui
.
ParagraphConstraints
(
width:
maxWidth
));
if
(
minWidth
!=
maxWidth
)
{
if
(
minWidth
!=
maxWidth
)
{
final
double
newWidth
=
maxIntrinsicWidth
.
clamp
(
minWidth
,
maxWidth
);
final
double
newWidth
=
maxIntrinsicWidth
.
clamp
(
minWidth
,
maxWidth
);
if
(
newWidth
!=
width
)
if
(
newWidth
!=
width
)
{
_paragraph
.
layout
(
ui
.
ParagraphConstraints
(
width:
newWidth
));
_paragraph
.
layout
(
ui
.
ParagraphConstraints
(
width:
newWidth
));
}
}
}
}
_inlinePlaceholderBoxes
=
_paragraph
.
getBoxesForPlaceholders
();
}
/// Paints the text onto the given canvas at the given offset.
/// Paints the text onto the given canvas at the given offset.
///
///
...
@@ -491,7 +608,7 @@ class TextPainter {
...
@@ -491,7 +608,7 @@ class TextPainter {
// TODO(garyq): Use actual extended grapheme cluster length instead of
// TODO(garyq): Use actual extended grapheme cluster length instead of
// an increasing cluster length amount to achieve deterministic performance.
// an increasing cluster length amount to achieve deterministic performance.
Rect
_getRectFromUpstream
(
int
offset
,
Rect
caretPrototype
)
{
Rect
_getRectFromUpstream
(
int
offset
,
Rect
caretPrototype
)
{
final
String
flattenedText
=
_text
.
toPlainText
();
final
String
flattenedText
=
_text
.
toPlainText
(
includePlaceholders:
false
);
final
int
prevCodeUnit
=
_text
.
codeUnitAt
(
max
(
0
,
offset
-
1
));
final
int
prevCodeUnit
=
_text
.
codeUnitAt
(
max
(
0
,
offset
-
1
));
if
(
prevCodeUnit
==
null
)
if
(
prevCodeUnit
==
null
)
return
null
;
return
null
;
...
@@ -507,10 +624,12 @@ class TextPainter {
...
@@ -507,10 +624,12 @@ class TextPainter {
if
(
boxes
.
isEmpty
)
{
if
(
boxes
.
isEmpty
)
{
// When we are at the beginning of the line, a non-surrogate position will
// When we are at the beginning of the line, a non-surrogate position will
// return empty boxes. We break and try from downstream instead.
// return empty boxes. We break and try from downstream instead.
if
(!
needsSearch
)
if
(!
needsSearch
)
{
break
;
// Only perform one iteration if no search is required.
break
;
// Only perform one iteration if no search is required.
if
(
prevRuneOffset
<
-
flattenedText
.
length
)
}
if
(
prevRuneOffset
<
-
flattenedText
.
length
)
{
break
;
// Stop iterating when beyond the max length of the text.
break
;
// Stop iterating when beyond the max length of the text.
}
// Multiply by two to log(n) time cover the entire text span. This allows
// Multiply by two to log(n) time cover the entire text span. This allows
// faster discovery of very long clusters and reduces the possibility
// faster discovery of very long clusters and reduces the possibility
// of certain large clusters taking much longer than others, which can
// of certain large clusters taking much longer than others, which can
...
@@ -538,7 +657,7 @@ class TextPainter {
...
@@ -538,7 +657,7 @@ class TextPainter {
// TODO(garyq): Use actual extended grapheme cluster length instead of
// TODO(garyq): Use actual extended grapheme cluster length instead of
// an increasing cluster length amount to achieve deterministic performance.
// an increasing cluster length amount to achieve deterministic performance.
Rect
_getRectFromDownstream
(
int
offset
,
Rect
caretPrototype
)
{
Rect
_getRectFromDownstream
(
int
offset
,
Rect
caretPrototype
)
{
final
String
flattenedText
=
_text
.
toPlainText
();
final
String
flattenedText
=
_text
.
toPlainText
(
includePlaceholders:
false
);
// We cap the offset at the final index of the _text.
// We cap the offset at the final index of the _text.
final
int
nextCodeUnit
=
_text
.
codeUnitAt
(
min
(
offset
,
flattenedText
==
null
?
0
:
flattenedText
.
length
-
1
));
final
int
nextCodeUnit
=
_text
.
codeUnitAt
(
min
(
offset
,
flattenedText
==
null
?
0
:
flattenedText
.
length
-
1
));
if
(
nextCodeUnit
==
null
)
if
(
nextCodeUnit
==
null
)
...
@@ -554,10 +673,12 @@ class TextPainter {
...
@@ -554,10 +673,12 @@ class TextPainter {
if
(
boxes
.
isEmpty
)
{
if
(
boxes
.
isEmpty
)
{
// When we are at the end of the line, a non-surrogate position will
// When we are at the end of the line, a non-surrogate position will
// return empty boxes. We break and try from upstream instead.
// return empty boxes. We break and try from upstream instead.
if
(!
needsSearch
)
if
(!
needsSearch
)
{
break
;
// Only perform one iteration if no search is required.
break
;
// Only perform one iteration if no search is required.
if
(
nextRuneOffset
>=
flattenedText
.
length
<<
1
)
}
if
(
nextRuneOffset
>=
flattenedText
.
length
<<
1
)
{
break
;
// Stop iterating when beyond the max length of the text.
break
;
// Stop iterating when beyond the max length of the text.
}
// Multiply by two to log(n) time cover the entire text span. This allows
// Multiply by two to log(n) time cover the entire text span. This allows
// faster discovery of very long clusters and reduces the possibility
// faster discovery of very long clusters and reduces the possibility
// of certain large clusters taking much longer than others, which can
// of certain large clusters taking much longer than others, which can
...
...
packages/flutter/lib/src/painting/text_span.dart
View file @
14414f35
...
@@ -9,6 +9,8 @@ import 'package:flutter/gestures.dart';
...
@@ -9,6 +9,8 @@ import 'package:flutter/gestures.dart';
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'basic_types.dart'
;
import
'basic_types.dart'
;
import
'inline_span.dart'
;
import
'text_painter.dart'
;
import
'text_style.dart'
;
import
'text_style.dart'
;
/// An immutable span of text.
/// An immutable span of text.
...
@@ -21,7 +23,9 @@ import 'text_style.dart';
...
@@ -21,7 +23,9 @@ import 'text_style.dart';
/// only partially) override the [style] of this object. If a
/// only partially) override the [style] of this object. If a
/// [TextSpan] has both [text] and [children], then the [text] is
/// [TextSpan] has both [text] and [children], then the [text] is
/// treated as if it was an unstyled [TextSpan] at the start of the
/// treated as if it was an unstyled [TextSpan] at the start of the
/// [children] list.
/// [children] list. Leaving the [TextSpan.text] field null results
/// in the [TextSpan] acting as an empty node in the [InlineSpan]
/// tree with a list of children.
///
///
/// To paint a [TextSpan] on a [Canvas], use a [TextPainter]. To display a text
/// To paint a [TextSpan] on a [Canvas], use a [TextPainter]. To display a text
/// span in a widget, use a [RichText]. For text with a single style, consider
/// span in a widget, use a [RichText]. For text with a single style, consider
...
@@ -42,27 +46,33 @@ import 'text_style.dart';
...
@@ -42,27 +46,33 @@ import 'text_style.dart';
/// _There is some more detailed sample code in the documentation for the
/// _There is some more detailed sample code in the documentation for the
/// [recognizer] property._
/// [recognizer] property._
///
///
/// The [TextSpan.text] will be used as the semantics label unless overriden
/// by the [TextSpan.semanticsLabel] property. Any [PlaceholderSpan]s in the
/// [TextSpan.children] list will separate the text before and after it into
/// two semantics nodes.
///
/// See also:
/// See also:
///
///
/// * [WidgetSpan], a leaf node that represents an embedded inline widget
/// in an [InlineSpan] tree. Specify a widget within the [children]
/// list by wrapping the widget with a [WidgetSpan]. The widget will be
/// laid out inline within the paragraph.
/// * [Text], a widget for showing uniformly-styled text.
/// * [Text], a widget for showing uniformly-styled text.
/// * [RichText], a widget for finer control of text rendering.
/// * [RichText], a widget for finer control of text rendering.
/// * [TextPainter], a class for painting [TextSpan] objects on a [Canvas].
/// * [TextPainter], a class for painting [TextSpan] objects on a [Canvas].
@immutable
@immutable
class
TextSpan
extends
DiagnosticableTree
{
class
TextSpan
extends
InlineSpan
{
/// Creates a [TextSpan] with the given values.
/// Creates a [TextSpan] with the given values.
///
///
/// For the object to be useful, at least one of [text] or
/// For the object to be useful, at least one of [text] or
/// [children] should be set.
/// [children] should be set.
const
TextSpan
({
const
TextSpan
({
this
.
style
,
this
.
text
,
this
.
text
,
this
.
children
,
this
.
children
,
TextStyle
style
,
this
.
recognizer
,
this
.
recognizer
,
this
.
semanticsLabel
,
this
.
semanticsLabel
,
});
})
:
super
(
style:
style
,);
/// The style to apply to the [text] and the [children].
final
TextStyle
style
;
/// The text contained in the span.
/// The text contained in the span.
///
///
...
@@ -79,26 +89,26 @@ class TextSpan extends DiagnosticableTree {
...
@@ -79,26 +89,26 @@ class TextSpan extends DiagnosticableTree {
/// supported and may have unexpected results.
/// supported and may have unexpected results.
///
///
/// The list must not contain any nulls.
/// The list must not contain any nulls.
final
List
<
Text
Span
>
children
;
final
List
<
Inline
Span
>
children
;
/// A gesture recognizer that will receive events that hit this
text
span.
/// A gesture recognizer that will receive events that hit this span.
///
///
/// [
Text
Span] itself does not implement hit testing or event dispatch. The
/// [
Inline
Span] itself does not implement hit testing or event dispatch. The
/// object that manages the [
Text
Span] painting is also responsible for
/// object that manages the [
Inline
Span] painting is also responsible for
/// dispatching events. In the rendering library, that is the
/// dispatching events. In the rendering library, that is the
/// [RenderParagraph] object, which corresponds to the [RichText] widget in
/// [RenderParagraph] object, which corresponds to the [RichText] widget in
/// the widgets layer; these objects do not bubble events in [
Text
Span]s, so a
/// the widgets layer; these objects do not bubble events in [
Inline
Span]s, so a
/// [recognizer] is only effective for events that directly hit the [text] of
/// [recognizer] is only effective for events that directly hit the [text] of
/// that [
Text
Span], not any of its [children].
/// that [
Inline
Span], not any of its [children].
///
///
/// [
Text
Span] also does not manage the lifetime of the gesture recognizer.
/// [
Inline
Span] also does not manage the lifetime of the gesture recognizer.
/// The code that owns the [GestureRecognizer] object must call
/// The code that owns the [GestureRecognizer] object must call
/// [GestureRecognizer.dispose] when the [
Text
Span] object is no longer used.
/// [GestureRecognizer.dispose] when the [
Inline
Span] object is no longer used.
///
///
/// {@tool sample}
/// {@tool sample}
///
///
/// This example shows how to manage the lifetime of a gesture recognizer
/// This example shows how to manage the lifetime of a gesture recognizer
/// provided to a
[Text
Span] object. It defines a `BuzzingText` widget which
/// provided to a
n [Inline
Span] object. It defines a `BuzzingText` widget which
/// uses the [HapticFeedback] class to vibrate the device when the user
/// uses the [HapticFeedback] class to vibrate the device when the user
/// long-presses the "find the" span, which is underlined in wavy green. The
/// long-presses the "find the" span, which is underlined in wavy green. The
/// hit-testing is handled by the [RichText] widget.
/// hit-testing is handled by the [RichText] widget.
...
@@ -131,11 +141,11 @@ class TextSpan extends DiagnosticableTree {
...
@@ -131,11 +141,11 @@ class TextSpan extends DiagnosticableTree {
///
///
/// @override
/// @override
/// Widget build(BuildContext context) {
/// Widget build(BuildContext context) {
/// return
RichText
(
/// return
Text.rich
(
///
text:
TextSpan(
/// TextSpan(
/// text: 'Can you ',
/// text: 'Can you ',
/// style: TextStyle(color: Colors.black),
/// style: TextStyle(color: Colors.black),
/// children: <
Text
Span>[
/// children: <
Inline
Span>[
/// TextSpan(
/// TextSpan(
/// text: 'find the',
/// text: 'find the',
/// style: TextStyle(
/// style: TextStyle(
...
@@ -157,7 +167,7 @@ class TextSpan extends DiagnosticableTree {
...
@@ -157,7 +167,7 @@ class TextSpan extends DiagnosticableTree {
/// {@end-tool}
/// {@end-tool}
final
GestureRecognizer
recognizer
;
final
GestureRecognizer
recognizer
;
/// An alternative semantics label for this
text
.
/// An alternative semantics label for this
[TextSpan]
.
///
///
/// If present, the semantics of this span will contain this value instead
/// If present, the semantics of this span will contain this value instead
/// of the actual text.
/// of the actual text.
...
@@ -177,7 +187,8 @@ class TextSpan extends DiagnosticableTree {
...
@@ -177,7 +187,8 @@ class TextSpan extends DiagnosticableTree {
/// Rather than using this directly, it's simpler to use the
/// Rather than using this directly, it's simpler to use the
/// [TextPainter] class to paint [TextSpan] objects onto [Canvas]
/// [TextPainter] class to paint [TextSpan] objects onto [Canvas]
/// objects.
/// objects.
void
build
(
ui
.
ParagraphBuilder
builder
,
{
double
textScaleFactor
=
1.0
})
{
@override
void
build
(
ui
.
ParagraphBuilder
builder
,
{
double
textScaleFactor
=
1.0
,
List
<
PlaceholderDimensions
>
dimensions
})
{
assert
(
debugAssertIsValid
());
assert
(
debugAssertIsValid
());
final
bool
hasStyle
=
style
!=
null
;
final
bool
hasStyle
=
style
!=
null
;
if
(
hasStyle
)
if
(
hasStyle
)
...
@@ -185,9 +196,9 @@ class TextSpan extends DiagnosticableTree {
...
@@ -185,9 +196,9 @@ class TextSpan extends DiagnosticableTree {
if
(
text
!=
null
)
if
(
text
!=
null
)
builder
.
addText
(
text
);
builder
.
addText
(
text
);
if
(
children
!=
null
)
{
if
(
children
!=
null
)
{
for
(
Text
Span
child
in
children
)
{
for
(
Inline
Span
child
in
children
)
{
assert
(
child
!=
null
);
assert
(
child
!=
null
);
child
.
build
(
builder
,
textScaleFactor:
textScaleFactor
);
child
.
build
(
builder
,
textScaleFactor:
textScaleFactor
,
dimensions:
dimensions
);
}
}
}
}
if
(
hasStyle
)
if
(
hasStyle
)
...
@@ -196,14 +207,15 @@ class TextSpan extends DiagnosticableTree {
...
@@ -196,14 +207,15 @@ class TextSpan extends DiagnosticableTree {
/// Walks this text span and its descendants in pre-order and calls [visitor]
/// Walks this text span and its descendants in pre-order and calls [visitor]
/// for each span that has text.
/// for each span that has text.
bool
visitTextSpan
(
bool
visitor
(
TextSpan
span
))
{
@override
bool
visitChildren
(
InlineSpanVisitor
visitor
)
{
if
(
text
!=
null
)
{
if
(
text
!=
null
)
{
if
(!
visitor
(
this
))
if
(!
visitor
(
this
))
return
false
;
return
false
;
}
}
if
(
children
!=
null
)
{
if
(
children
!=
null
)
{
for
(
Text
Span
child
in
children
)
{
for
(
Inline
Span
child
in
children
)
{
if
(!
child
.
visit
TextSpa
n
(
visitor
))
if
(!
child
.
visit
Childre
n
(
visitor
))
return
false
;
return
false
;
}
}
}
}
...
@@ -211,63 +223,62 @@ class TextSpan extends DiagnosticableTree {
...
@@ -211,63 +223,62 @@ class TextSpan extends DiagnosticableTree {
}
}
/// Returns the text span that contains the given position in the text.
/// Returns the text span that contains the given position in the text.
TextSpan
getSpanForPosition
(
TextPosition
position
)
{
@override
assert
(
debugAssertIsValid
());
InlineSpan
getSpanForPositionVisitor
(
TextPosition
position
,
Accumulator
offset
)
{
if
(
text
==
null
)
{
return
null
;
}
final
TextAffinity
affinity
=
position
.
affinity
;
final
TextAffinity
affinity
=
position
.
affinity
;
final
int
targetOffset
=
position
.
offset
;
final
int
targetOffset
=
position
.
offset
;
int
offset
=
0
;
final
int
endOffset
=
offset
.
value
+
text
.
length
;
TextSpan
result
;
if
(
offset
.
value
==
targetOffset
&&
affinity
==
TextAffinity
.
downstream
||
visitTextSpan
((
TextSpan
span
)
{
offset
.
value
<
targetOffset
&&
targetOffset
<
endOffset
||
assert
(
result
==
null
);
endOffset
==
targetOffset
&&
affinity
==
TextAffinity
.
upstream
)
{
final
int
endOffset
=
offset
+
span
.
text
.
length
;
return
this
;
if
(
targetOffset
==
offset
&&
affinity
==
TextAffinity
.
downstream
||
targetOffset
>
offset
&&
targetOffset
<
endOffset
||
targetOffset
==
endOffset
&&
affinity
==
TextAffinity
.
upstream
)
{
result
=
span
;
return
false
;
}
}
offset
=
endOffset
;
offset
.
increment
(
text
.
length
);
return
true
;
return
null
;
});
return
result
;
}
}
/// Flattens the [TextSpan] tree into a single string.
@override
///
void
computeToPlainText
(
StringBuffer
buffer
,
{
bool
includeSemanticsLabels
=
true
,
bool
includePlaceholders
=
true
})
{
/// Styles are not honored in this process. If `includeSemanticsLabels` is
/// true, then the text returned will include the [semanticsLabel]s instead of
/// the text contents when they are present.
String
toPlainText
({
bool
includeSemanticsLabels
=
true
})
{
assert
(
debugAssertIsValid
());
assert
(
debugAssertIsValid
());
final
StringBuffer
buffer
=
StringBuffer
();
if
(
semanticsLabel
!=
null
&&
includeSemanticsLabels
)
{
visitTextSpan
((
TextSpan
span
)
{
buffer
.
write
(
semanticsLabel
);
if
(
span
.
semanticsLabel
!=
null
&&
includeSemanticsLabels
)
{
}
else
if
(
text
!=
null
)
{
buffer
.
write
(
span
.
semanticsLabel
);
buffer
.
write
(
text
);
}
else
{
}
buffer
.
write
(
span
.
text
);
if
(
children
!=
null
)
{
for
(
InlineSpan
child
in
children
)
{
child
.
computeToPlainText
(
buffer
,
includeSemanticsLabels:
includeSemanticsLabels
,
includePlaceholders:
includePlaceholders
,
);
}
}
}
return
true
;
});
return
buffer
.
toString
();
}
}
/// Returns the UTF-16 code unit at the given index in the flattened string.
@override
///
int
codeUnitAtVisitor
(
int
index
,
Accumulator
offset
)
{
/// Returns null if the index is out of bounds.
if
(
text
==
null
)
{
int
codeUnitAt
(
int
index
)
{
if
(
index
<
0
)
return
null
;
return
null
;
int
offset
=
0
;
int
result
;
visitTextSpan
((
TextSpan
span
)
{
if
(
index
-
offset
<
span
.
text
.
length
)
{
result
=
span
.
text
.
codeUnitAt
(
index
-
offset
);
return
false
;
}
}
offset
+=
span
.
text
.
length
;
if
(
index
-
offset
.
value
<
text
.
length
)
{
return
true
;
return
text
.
codeUnitAt
(
index
-
offset
.
value
);
});
}
return
result
;
offset
.
increment
(
text
.
length
);
return
null
;
}
@override
void
describeSemantics
(
Accumulator
offset
,
List
<
int
>
semanticsOffsets
,
List
<
dynamic
>
semanticsElements
)
{
if
(
recognizer
!=
null
&&
(
recognizer
is
TapGestureRecognizer
||
recognizer
is
LongPressGestureRecognizer
))
{
final
int
length
=
semanticsLabel
?.
length
??
text
.
length
;
semanticsOffsets
.
add
(
offset
.
value
);
semanticsOffsets
.
add
(
offset
.
value
+
length
);
semanticsElements
.
add
(
recognizer
);
}
offset
.
increment
(
text
!=
null
?
text
.
length
:
0
);
}
}
/// In checked mode, throws an exception if the object is not in a
/// In checked mode, throws an exception if the object is not in a
...
@@ -278,45 +289,39 @@ class TextSpan extends DiagnosticableTree {
...
@@ -278,45 +289,39 @@ class TextSpan extends DiagnosticableTree {
/// ```dart
/// ```dart
/// assert(myTextSpan.debugAssertIsValid());
/// assert(myTextSpan.debugAssertIsValid());
/// ```
/// ```
@override
bool
debugAssertIsValid
()
{
bool
debugAssertIsValid
()
{
assert
(()
{
assert
(()
{
if
(!
visitTextSpan
((
TextSpan
span
)
{
if
(
children
!=
null
)
{
if
(
span
.
children
!=
null
)
{
for
(
InlineSpan
child
in
children
)
{
for
(
TextSpan
child
in
span
.
children
)
{
assert
(
child
!=
null
,
if
(
child
==
null
)
'TextSpan contains a null child.
\n
...'
return
false
;
}
}
return
true
;
}))
{
throw
FlutterError
(
'TextSpan contains a null child.
\n
'
'A TextSpan object with a non-null child list should not have any nulls in its child list.
\n
'
'A TextSpan object with a non-null child list should not have any nulls in its child list.
\n
'
'The full text in question was:
\n
'
'The full text in question was:
\n
'
'
${toStringDeep(prefixLineOne: ' ')}
'
'
${toStringDeep(prefixLineOne: ' ')}
'
);
);
assert
(
child
.
debugAssertIsValid
());
}
}
}
return
true
;
return
true
;
}());
}());
return
true
;
return
super
.
debugAssertIsValid
()
;
}
}
/// Describe the difference between this text span and another, in terms of
@override
/// how much damage it will make to the rendering. The comparison is deep.
RenderComparison
compareTo
(
InlineSpan
other
)
{
///
/// See also:
///
/// * [TextStyle.compareTo], which does the same thing for [TextStyle]s.
RenderComparison
compareTo
(
TextSpan
other
)
{
if
(
identical
(
this
,
other
))
if
(
identical
(
this
,
other
))
return
RenderComparison
.
identical
;
return
RenderComparison
.
identical
;
if
(
other
.
text
!=
text
||
if
(
other
.
runtimeType
!=
runtimeType
)
children
?.
length
!=
other
.
children
?.
length
||
(
style
==
null
)
!=
(
other
.
style
==
null
))
return
RenderComparison
.
layout
;
return
RenderComparison
.
layout
;
RenderComparison
result
=
recognizer
==
other
.
recognizer
?
RenderComparison
.
identical
:
RenderComparison
.
metadata
;
final
TextSpan
textSpan
=
other
;
if
(
textSpan
.
text
!=
text
||
children
?.
length
!=
textSpan
.
children
?.
length
||
(
style
==
null
)
!=
(
textSpan
.
style
==
null
))
return
RenderComparison
.
layout
;
RenderComparison
result
=
recognizer
==
textSpan
.
recognizer
?
RenderComparison
.
identical
:
RenderComparison
.
metadata
;
if
(
style
!=
null
)
{
if
(
style
!=
null
)
{
final
RenderComparison
candidate
=
style
.
compareTo
(
other
.
style
);
final
RenderComparison
candidate
=
style
.
compareTo
(
textSpan
.
style
);
if
(
candidate
.
index
>
result
.
index
)
if
(
candidate
.
index
>
result
.
index
)
result
=
candidate
;
result
=
candidate
;
if
(
result
==
RenderComparison
.
layout
)
if
(
result
==
RenderComparison
.
layout
)
...
@@ -324,7 +329,7 @@ class TextSpan extends DiagnosticableTree {
...
@@ -324,7 +329,7 @@ class TextSpan extends DiagnosticableTree {
}
}
if
(
children
!=
null
)
{
if
(
children
!=
null
)
{
for
(
int
index
=
0
;
index
<
children
.
length
;
index
+=
1
)
{
for
(
int
index
=
0
;
index
<
children
.
length
;
index
+=
1
)
{
final
RenderComparison
candidate
=
children
[
index
].
compareTo
(
other
.
children
[
index
]);
final
RenderComparison
candidate
=
children
[
index
].
compareTo
(
textSpan
.
children
[
index
]);
if
(
candidate
.
index
>
result
.
index
)
if
(
candidate
.
index
>
result
.
index
)
result
=
candidate
;
result
=
candidate
;
if
(
result
==
RenderComparison
.
layout
)
if
(
result
==
RenderComparison
.
layout
)
...
@@ -340,16 +345,17 @@ class TextSpan extends DiagnosticableTree {
...
@@ -340,16 +345,17 @@ class TextSpan extends DiagnosticableTree {
return
true
;
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
false
;
if
(
super
!=
other
)
return
false
;
final
TextSpan
typedOther
=
other
;
final
TextSpan
typedOther
=
other
;
return
typedOther
.
text
==
text
return
typedOther
.
text
==
text
&&
typedOther
.
style
==
style
&&
typedOther
.
recognizer
==
recognizer
&&
typedOther
.
recognizer
==
recognizer
&&
typedOther
.
semanticsLabel
==
semanticsLabel
&&
typedOther
.
semanticsLabel
==
semanticsLabel
&&
listEquals
<
Text
Span
>(
typedOther
.
children
,
children
);
&&
listEquals
<
Inline
Span
>(
typedOther
.
children
,
children
);
}
}
@override
@override
int
get
hashCode
=>
hashValues
(
s
tyl
e
,
text
,
recognizer
,
semanticsLabel
,
hashList
(
children
));
int
get
hashCode
=>
hashValues
(
s
uper
.
hashCod
e
,
text
,
recognizer
,
semanticsLabel
,
hashList
(
children
));
@override
@override
String
toStringShort
()
=>
'
$runtimeType
'
;
String
toStringShort
()
=>
'
$runtimeType
'
;
...
@@ -357,11 +363,10 @@ class TextSpan extends DiagnosticableTree {
...
@@ -357,11 +363,10 @@ class TextSpan extends DiagnosticableTree {
@override
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
super
.
debugFillProperties
(
properties
);
properties
.
defaultDiagnosticsTreeStyle
=
DiagnosticsTreeStyle
.
whitespace
;
// Properties on style are added as if they were properties directly on
properties
.
add
(
StringProperty
(
'text'
,
text
,
showName:
false
,
defaultValue:
null
));
// this TextSpan.
if
(
style
==
null
&&
text
==
null
&&
children
==
null
)
if
(
style
!=
null
)
properties
.
add
(
DiagnosticsNode
.
message
(
'(empty)'
));
style
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
GestureRecognizer
>(
properties
.
add
(
DiagnosticsProperty
<
GestureRecognizer
>(
'recognizer'
,
recognizer
,
'recognizer'
,
recognizer
,
...
@@ -369,22 +374,16 @@ class TextSpan extends DiagnosticableTree {
...
@@ -369,22 +374,16 @@ class TextSpan extends DiagnosticableTree {
defaultValue:
null
,
defaultValue:
null
,
));
));
if
(
semanticsLabel
!=
null
)
{
if
(
semanticsLabel
!=
null
)
{
properties
.
add
(
StringProperty
(
'semanticsLabel'
,
semanticsLabel
));
properties
.
add
(
StringProperty
(
'semanticsLabel'
,
semanticsLabel
));
}
}
properties
.
add
(
StringProperty
(
'text'
,
text
,
showName:
false
,
defaultValue:
null
));
if
(
style
==
null
&&
text
==
null
&&
children
==
null
)
properties
.
add
(
DiagnosticsNode
.
message
(
'(empty)'
));
}
}
@override
@override
List
<
DiagnosticsNode
>
debugDescribeChildren
()
{
List
<
DiagnosticsNode
>
debugDescribeChildren
()
{
if
(
children
==
null
)
if
(
children
==
null
)
return
const
<
DiagnosticsNode
>[];
return
const
<
DiagnosticsNode
>[];
return
children
.
map
<
DiagnosticsNode
>((
Text
Span
child
)
{
return
children
.
map
<
DiagnosticsNode
>((
Inline
Span
child
)
{
if
(
child
!=
null
)
{
if
(
child
!=
null
)
{
return
child
.
toDiagnosticsNode
();
return
child
.
toDiagnosticsNode
();
}
else
{
}
else
{
...
...
packages/flutter/lib/src/rendering/box.dart
View file @
14414f35
...
@@ -1728,7 +1728,10 @@ abstract class RenderBox extends RenderObject {
...
@@ -1728,7 +1728,10 @@ abstract class RenderBox extends RenderObject {
return
true
;
return
true
;
}());
}());
_size
=
value
;
_size
=
value
;
assert
(()
{
debugAssertDoesMeetConstraints
();
return
true
;
}());
assert
(()
{
debugAssertDoesMeetConstraints
();
return
true
;
}());
}
}
/// Claims ownership of the given [Size].
/// Claims ownership of the given [Size].
...
...
packages/flutter/lib/src/rendering/debug_overflow_indicator.dart
View file @
14414f35
...
@@ -284,8 +284,8 @@ mixin DebugOverflowIndicatorMixin on RenderObject {
...
@@ -284,8 +284,8 @@ mixin DebugOverflowIndicatorMixin on RenderObject {
final
List
<
_OverflowRegionData
>
overflowRegions
=
_calculateOverflowRegions
(
overflow
,
containerRect
);
final
List
<
_OverflowRegionData
>
overflowRegions
=
_calculateOverflowRegions
(
overflow
,
containerRect
);
for
(
_OverflowRegionData
region
in
overflowRegions
)
{
for
(
_OverflowRegionData
region
in
overflowRegions
)
{
context
.
canvas
.
drawRect
(
region
.
rect
.
shift
(
offset
),
_indicatorPaint
);
context
.
canvas
.
drawRect
(
region
.
rect
.
shift
(
offset
),
_indicatorPaint
);
final
TextSpan
textSpan
=
_indicatorLabel
[
region
.
side
.
index
].
text
;
if
(
_indicatorLabel
[
region
.
side
.
index
].
text
?.
text
!=
region
.
label
)
{
if
(
textSpan
?.
text
!=
region
.
label
)
{
_indicatorLabel
[
region
.
side
.
index
].
text
=
TextSpan
(
_indicatorLabel
[
region
.
side
.
index
].
text
=
TextSpan
(
text:
region
.
label
,
text:
region
.
label
,
style:
_indicatorTextStyle
,
style:
_indicatorTextStyle
,
...
...
packages/flutter/lib/src/rendering/paragraph.dart
View file @
14414f35
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:ui'
as
ui
show
Gradient
,
Shader
,
TextBox
;
import
'dart:ui'
as
ui
show
Gradient
,
Shader
,
TextBox
,
PlaceholderAlignment
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/gestures.dart'
;
...
@@ -10,6 +10,7 @@ import 'package:flutter/painting.dart';
...
@@ -10,6 +10,7 @@ import 'package:flutter/painting.dart';
import
'package:flutter/semantics.dart'
;
import
'package:flutter/semantics.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'package:vector_math/vector_math_64.dart'
;
import
'box.dart'
;
import
'box.dart'
;
import
'debug.dart'
;
import
'debug.dart'
;
...
@@ -35,8 +36,27 @@ enum TextOverflow {
...
@@ -35,8 +36,27 @@ enum TextOverflow {
const
String
_kEllipsis
=
'
\
u2026'
;
const
String
_kEllipsis
=
'
\
u2026'
;
/// A render object that displays a paragraph of text
/// Parent data for use with [RenderParagraph].
class
RenderParagraph
extends
RenderBox
{
class
TextParentData
extends
ContainerBoxParentData
<
RenderBox
>
{
/// The scaling of the text.
double
scale
;
@override
String
toString
()
{
final
List
<
String
>
values
=
<
String
>[];
if
(
offset
!=
null
)
values
.
add
(
'offset=
$offset
'
);
if
(
scale
!=
null
)
values
.
add
(
'scale=
$scale
'
);
values
.
add
(
super
.
toString
());
return
values
.
join
(
'; '
);
}
}
/// A render object that displays a paragraph of text.
class
RenderParagraph
extends
RenderBox
with
ContainerRenderObjectMixin
<
RenderBox
,
TextParentData
>,
RenderBoxContainerDefaultsMixin
<
RenderBox
,
TextParentData
>
{
/// Creates a paragraph render object.
/// Creates a paragraph render object.
///
///
/// The [text], [textAlign], [textDirection], [overflow], [softWrap], and
/// The [text], [textAlign], [textDirection], [overflow], [softWrap], and
...
@@ -44,8 +64,7 @@ class RenderParagraph extends RenderBox {
...
@@ -44,8 +64,7 @@ class RenderParagraph extends RenderBox {
///
///
/// The [maxLines] property may be null (and indeed defaults to null), but if
/// The [maxLines] property may be null (and indeed defaults to null), but if
/// it is not null, it must be greater than zero.
/// it is not null, it must be greater than zero.
RenderParagraph
(
RenderParagraph
(
InlineSpan
text
,
{
TextSpan
text
,
{
TextAlign
textAlign
=
TextAlign
.
start
,
TextAlign
textAlign
=
TextAlign
.
start
,
@required
TextDirection
textDirection
,
@required
TextDirection
textDirection
,
bool
softWrap
=
true
,
bool
softWrap
=
true
,
...
@@ -55,6 +74,7 @@ class RenderParagraph extends RenderBox {
...
@@ -55,6 +74,7 @@ class RenderParagraph extends RenderBox {
TextWidthBasis
textWidthBasis
=
TextWidthBasis
.
parent
,
TextWidthBasis
textWidthBasis
=
TextWidthBasis
.
parent
,
Locale
locale
,
Locale
locale
,
StrutStyle
strutStyle
,
StrutStyle
strutStyle
,
List
<
RenderBox
>
children
,
})
:
assert
(
text
!=
null
),
})
:
assert
(
text
!=
null
),
assert
(
text
.
debugAssertIsValid
()),
assert
(
text
.
debugAssertIsValid
()),
assert
(
textAlign
!=
null
),
assert
(
textAlign
!=
null
),
...
@@ -76,13 +96,22 @@ class RenderParagraph extends RenderBox {
...
@@ -76,13 +96,22 @@ class RenderParagraph extends RenderBox {
locale:
locale
,
locale:
locale
,
strutStyle:
strutStyle
,
strutStyle:
strutStyle
,
textWidthBasis:
textWidthBasis
,
textWidthBasis:
textWidthBasis
,
);
)
{
addAll
(
children
);
_extractPlaceholderSpans
(
text
);
}
@override
void
setupParentData
(
RenderBox
child
)
{
if
(
child
.
parentData
is
!
TextParentData
)
child
.
parentData
=
TextParentData
();
}
final
TextPainter
_textPainter
;
final
TextPainter
_textPainter
;
/// The text to display
/// The text to display
Text
Span
get
text
=>
_textPainter
.
text
;
Inline
Span
get
text
=>
_textPainter
.
text
;
set
text
(
Text
Span
value
)
{
set
text
(
Inline
Span
value
)
{
assert
(
value
!=
null
);
assert
(
value
!=
null
);
switch
(
_textPainter
.
text
.
compareTo
(
value
))
{
switch
(
_textPainter
.
text
.
compareTo
(
value
))
{
case
RenderComparison
.
identical
:
case
RenderComparison
.
identical
:
...
@@ -90,17 +119,31 @@ class RenderParagraph extends RenderBox {
...
@@ -90,17 +119,31 @@ class RenderParagraph extends RenderBox {
return
;
return
;
case
RenderComparison
.
paint
:
case
RenderComparison
.
paint
:
_textPainter
.
text
=
value
;
_textPainter
.
text
=
value
;
_extractPlaceholderSpans
(
value
);
markNeedsPaint
();
markNeedsPaint
();
markNeedsSemanticsUpdate
();
markNeedsSemanticsUpdate
();
break
;
break
;
case
RenderComparison
.
layout
:
case
RenderComparison
.
layout
:
_textPainter
.
text
=
value
;
_textPainter
.
text
=
value
;
_overflowShader
=
null
;
_overflowShader
=
null
;
_extractPlaceholderSpans
(
value
);
markNeedsLayout
();
markNeedsLayout
();
break
;
break
;
}
}
}
}
List
<
PlaceholderSpan
>
_placeholderSpans
;
void
_extractPlaceholderSpans
(
InlineSpan
span
)
{
_placeholderSpans
=
<
PlaceholderSpan
>[];
span
.
visitChildren
((
InlineSpan
span
)
{
if
(
span
is
PlaceholderSpan
)
{
final
PlaceholderSpan
placeholderSpan
=
span
;
_placeholderSpans
.
add
(
placeholderSpan
);
}
return
true
;
});
}
/// How the text should be aligned horizontally.
/// How the text should be aligned horizontally.
TextAlign
get
textAlign
=>
_textPainter
.
textAlign
;
TextAlign
get
textAlign
=>
_textPainter
.
textAlign
;
set
textAlign
(
TextAlign
value
)
{
set
textAlign
(
TextAlign
value
)
{
...
@@ -229,28 +272,31 @@ class RenderParagraph extends RenderBox {
...
@@ -229,28 +272,31 @@ class RenderParagraph extends RenderBox {
markNeedsLayout
();
markNeedsLayout
();
}
}
void
_layoutText
({
double
minWidth
=
0.0
,
double
maxWidth
=
double
.
infinity
})
{
final
bool
widthMatters
=
softWrap
||
overflow
==
TextOverflow
.
ellipsis
;
_textPainter
.
layout
(
minWidth:
minWidth
,
maxWidth:
widthMatters
?
maxWidth
:
double
.
infinity
);
}
void
_layoutTextWithConstraints
(
BoxConstraints
constraints
)
{
_layoutText
(
minWidth:
constraints
.
minWidth
,
maxWidth:
constraints
.
maxWidth
);
}
@override
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
double
computeMinIntrinsicWidth
(
double
height
)
{
_layoutText
();
if
(!
_canComputeIntrinsics
())
{
return
0.0
;
}
_computeChildrenWidthWithMinIntrinsics
(
height
);
_layoutText
();
// layout with infinite width.
return
_textPainter
.
minIntrinsicWidth
;
return
_textPainter
.
minIntrinsicWidth
;
}
}
@override
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
double
computeMaxIntrinsicWidth
(
double
height
)
{
_layoutText
();
if
(!
_canComputeIntrinsics
())
{
return
0.0
;
}
_computeChildrenWidthWithMaxIntrinsics
(
height
);
_layoutText
();
// layout with infinite width.
return
_textPainter
.
maxIntrinsicWidth
;
return
_textPainter
.
maxIntrinsicWidth
;
}
}
double
_computeIntrinsicHeight
(
double
width
)
{
double
_computeIntrinsicHeight
(
double
width
)
{
if
(!
_canComputeIntrinsics
())
{
return
0.0
;
}
_computeChildrenHeightWithMinIntrinsics
(
width
);
_layoutText
(
minWidth:
width
,
maxWidth:
width
);
_layoutText
(
minWidth:
width
,
maxWidth:
width
);
return
_textPainter
.
height
;
return
_textPainter
.
height
;
}
}
...
@@ -274,9 +320,114 @@ class RenderParagraph extends RenderBox {
...
@@ -274,9 +320,114 @@ class RenderParagraph extends RenderBox {
return
_textPainter
.
computeDistanceToActualBaseline
(
baseline
);
return
_textPainter
.
computeDistanceToActualBaseline
(
baseline
);
}
}
// Intrinsics cannot be calculated without a full layout for
// alignments that require the baseline (baseline, aboveBaseline,
// belowBaseline).
bool
_canComputeIntrinsics
()
{
for
(
PlaceholderSpan
span
in
_placeholderSpans
)
{
switch
(
span
.
alignment
)
{
case
ui
.
PlaceholderAlignment
.
baseline
:
case
ui
.
PlaceholderAlignment
.
aboveBaseline
:
case
ui
.
PlaceholderAlignment
.
belowBaseline
:
{
assert
(
RenderObject
.
debugCheckingIntrinsics
,
'Intrinsics are not available for PlaceholderAlignment.baseline, '
'PlaceholderAlignment.aboveBaseline, or PlaceholderAlignment.belowBaseline,'
);
return
false
;
}
case
ui
.
PlaceholderAlignment
.
top
:
case
ui
.
PlaceholderAlignment
.
middle
:
case
ui
.
PlaceholderAlignment
.
bottom
:
{
continue
;
}
}
}
return
true
;
}
void
_computeChildrenWidthWithMaxIntrinsics
(
double
height
)
{
RenderBox
child
=
firstChild
;
final
List
<
PlaceholderDimensions
>
placeholderDimensions
=
List
<
PlaceholderDimensions
>(
childCount
);
int
childIndex
=
0
;
while
(
child
!=
null
)
{
// Height and baseline is irrelevant as all text will be laid
// out in a single line.
placeholderDimensions
[
childIndex
]
=
PlaceholderDimensions
(
size:
Size
(
child
.
getMaxIntrinsicWidth
(
height
),
height
),
alignment:
_placeholderSpans
[
childIndex
].
alignment
,
baseline:
_placeholderSpans
[
childIndex
].
baseline
,
);
child
=
childAfter
(
child
);
childIndex
+=
1
;
}
_textPainter
.
setPlaceholderDimensions
(
placeholderDimensions
);
}
void
_computeChildrenWidthWithMinIntrinsics
(
double
height
)
{
RenderBox
child
=
firstChild
;
final
List
<
PlaceholderDimensions
>
placeholderDimensions
=
List
<
PlaceholderDimensions
>(
childCount
);
int
childIndex
=
0
;
while
(
child
!=
null
)
{
final
double
intrinsicWidth
=
child
.
getMinIntrinsicWidth
(
height
);
final
double
intrinsicHeight
=
child
.
getMinIntrinsicHeight
(
intrinsicWidth
);
placeholderDimensions
[
childIndex
]
=
PlaceholderDimensions
(
size:
Size
(
intrinsicWidth
,
intrinsicHeight
),
alignment:
_placeholderSpans
[
childIndex
].
alignment
,
baseline:
_placeholderSpans
[
childIndex
].
baseline
,
);
child
=
childAfter
(
child
);
childIndex
+=
1
;
}
_textPainter
.
setPlaceholderDimensions
(
placeholderDimensions
);
}
void
_computeChildrenHeightWithMinIntrinsics
(
double
width
)
{
RenderBox
child
=
firstChild
;
final
List
<
PlaceholderDimensions
>
placeholderDimensions
=
List
<
PlaceholderDimensions
>(
childCount
);
int
childIndex
=
0
;
while
(
child
!=
null
)
{
final
double
intrinsicHeight
=
child
.
getMinIntrinsicHeight
(
width
);
final
double
intrinsicWidth
=
child
.
getMinIntrinsicWidth
(
intrinsicHeight
);
placeholderDimensions
[
childIndex
]
=
PlaceholderDimensions
(
size:
Size
(
intrinsicWidth
,
intrinsicHeight
),
alignment:
_placeholderSpans
[
childIndex
].
alignment
,
baseline:
_placeholderSpans
[
childIndex
].
baseline
,
);
child
=
childAfter
(
child
);
childIndex
+=
1
;
}
_textPainter
.
setPlaceholderDimensions
(
placeholderDimensions
);
}
@override
@override
bool
hitTestSelf
(
Offset
position
)
=>
true
;
bool
hitTestSelf
(
Offset
position
)
=>
true
;
@override
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
Offset
position
})
{
RenderBox
child
=
firstChild
;
while
(
child
!=
null
)
{
final
TextParentData
textParentData
=
child
.
parentData
;
final
Matrix4
transform
=
Matrix4
.
translationValues
(
textParentData
.
offset
.
dx
,
textParentData
.
offset
.
dy
,
0.0
)
..
scale
(
textParentData
.
scale
,
textParentData
.
scale
,
textParentData
.
scale
);
final
bool
isHit
=
result
.
addWithPaintTransform
(
transform:
transform
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(()
{
final
Offset
manualPosition
=
(
position
-
textParentData
.
offset
)
/
textParentData
.
scale
;
return
(
transformed
.
dx
-
manualPosition
.
dx
).
abs
()
<
precisionErrorTolerance
&&
(
transformed
.
dy
-
manualPosition
.
dy
).
abs
()
<
precisionErrorTolerance
;
}());
return
child
.
hitTest
(
result
,
position:
transformed
);
},
);
if
(
isHit
)
{
return
true
;
}
child
=
childAfter
(
child
);
}
return
false
;
}
@override
@override
void
handleEvent
(
PointerEvent
event
,
BoxHitTestEntry
entry
)
{
void
handleEvent
(
PointerEvent
event
,
BoxHitTestEntry
entry
)
{
assert
(
debugHandleEvent
(
event
,
entry
));
assert
(
debugHandleEvent
(
event
,
entry
));
...
@@ -299,9 +450,81 @@ class RenderParagraph extends RenderBox {
...
@@ -299,9 +450,81 @@ class RenderParagraph extends RenderBox {
@visibleForTesting
@visibleForTesting
bool
get
debugHasOverflowShader
=>
_overflowShader
!=
null
;
bool
get
debugHasOverflowShader
=>
_overflowShader
!=
null
;
void
_layoutText
({
double
minWidth
=
0.0
,
double
maxWidth
=
double
.
infinity
})
{
final
bool
widthMatters
=
softWrap
||
overflow
==
TextOverflow
.
ellipsis
;
_textPainter
.
layout
(
minWidth:
minWidth
,
maxWidth:
widthMatters
?
maxWidth
:
double
.
infinity
);
}
void
_layoutTextWithConstraints
(
BoxConstraints
constraints
)
{
_layoutText
(
minWidth:
constraints
.
minWidth
,
maxWidth:
constraints
.
maxWidth
);
}
// Layout the child inline widgets. We then pass the dimensions of the
// children to _textPainter so that appropriate placeholders can be inserted
// into the LibTxt layout. This does not do anything if no inline widgets were
// specified.
void
_layoutChildren
(
BoxConstraints
constraints
)
{
if
(
childCount
==
0
)
{
return
;
}
RenderBox
child
=
firstChild
;
final
List
<
PlaceholderDimensions
>
placeholderDimensions
=
List
<
PlaceholderDimensions
>(
childCount
);
int
childIndex
=
0
;
while
(
child
!=
null
)
{
// Only constrain the width to the maximum width of the paragraph.
// Leave height unconstrained, which will overflow if expanded past.
child
.
layout
(
BoxConstraints
(
maxWidth:
constraints
.
maxWidth
,
),
parentUsesSize:
true
);
double
baselineOffset
;
switch
(
_placeholderSpans
[
childIndex
].
alignment
)
{
case
ui
.
PlaceholderAlignment
.
baseline
:
{
baselineOffset
=
child
.
getDistanceToBaseline
(
_placeholderSpans
[
childIndex
].
baseline
);
break
;
}
default
:
{
baselineOffset
=
null
;
break
;
}
}
placeholderDimensions
[
childIndex
]
=
PlaceholderDimensions
(
size:
child
.
size
,
alignment:
_placeholderSpans
[
childIndex
].
alignment
,
baseline:
_placeholderSpans
[
childIndex
].
baseline
,
baselineOffset:
baselineOffset
,
);
child
=
childAfter
(
child
);
childIndex
+=
1
;
}
_textPainter
.
setPlaceholderDimensions
(
placeholderDimensions
);
}
// Iterate through the laid-out children and set the parentData offsets based
// off of the placeholders inserted for each child.
void
_setParentData
()
{
RenderBox
child
=
firstChild
;
int
childIndex
=
0
;
while
(
child
!=
null
)
{
final
TextParentData
textParentData
=
child
.
parentData
;
textParentData
.
offset
=
Offset
(
_textPainter
.
inlinePlaceholderBoxes
[
childIndex
].
left
,
_textPainter
.
inlinePlaceholderBoxes
[
childIndex
].
top
);
textParentData
.
scale
=
_textPainter
.
inlinePlaceholderScales
[
childIndex
];
child
=
childAfter
(
child
);
childIndex
+=
1
;
}
}
@override
@override
void
performLayout
()
{
void
performLayout
()
{
_layoutChildren
(
constraints
);
_layoutTextWithConstraints
(
constraints
);
_layoutTextWithConstraints
(
constraints
);
_setParentData
();
// We grab _textPainter.size and _textPainter.didExceedMaxLines here because
// We grab _textPainter.size and _textPainter.didExceedMaxLines here because
// assigning to `size` will trigger us to validate our intrinsic sizes,
// assigning to `size` will trigger us to validate our intrinsic sizes,
// which will change _textPainter's layout because the intrinsic size
// which will change _textPainter's layout because the intrinsic size
...
@@ -386,13 +609,12 @@ class RenderParagraph extends RenderBox {
...
@@ -386,13 +609,12 @@ class RenderParagraph extends RenderBox {
// If you remove this call, make sure that changing the textAlign still
// If you remove this call, make sure that changing the textAlign still
// works properly.
// works properly.
_layoutTextWithConstraints
(
constraints
);
_layoutTextWithConstraints
(
constraints
);
final
Canvas
canvas
=
context
.
canvas
;
assert
(()
{
assert
(()
{
if
(
debugRepaintTextRainbowEnabled
)
{
if
(
debugRepaintTextRainbowEnabled
)
{
final
Paint
paint
=
Paint
()
final
Paint
paint
=
Paint
()
..
color
=
debugCurrentRepaintColor
.
toColor
();
..
color
=
debugCurrentRepaintColor
.
toColor
();
canvas
.
drawRect
(
offset
&
size
,
paint
);
c
ontext
.
c
anvas
.
drawRect
(
offset
&
size
,
paint
);
}
}
return
true
;
return
true
;
}());
}());
...
@@ -402,22 +624,44 @@ class RenderParagraph extends RenderBox {
...
@@ -402,22 +624,44 @@ class RenderParagraph extends RenderBox {
if
(
_overflowShader
!=
null
)
{
if
(
_overflowShader
!=
null
)
{
// This layer limits what the shader below blends with to be just the text
// This layer limits what the shader below blends with to be just the text
// (as opposed to the text and its background).
// (as opposed to the text and its background).
canvas
.
saveLayer
(
bounds
,
Paint
());
c
ontext
.
c
anvas
.
saveLayer
(
bounds
,
Paint
());
}
else
{
}
else
{
canvas
.
save
();
context
.
canvas
.
save
();
}
}
canvas
.
clipRect
(
bounds
);
context
.
canvas
.
clipRect
(
bounds
);
}
_textPainter
.
paint
(
context
.
canvas
,
offset
);
RenderBox
child
=
firstChild
;
int
childIndex
=
0
;
while
(
child
!=
null
)
{
assert
(
childIndex
<
_textPainter
.
inlinePlaceholderBoxes
.
length
);
final
TextParentData
textParentData
=
child
.
parentData
;
final
double
scale
=
textParentData
.
scale
;
context
.
pushTransform
(
needsCompositing
,
offset
+
textParentData
.
offset
,
Matrix4
.
diagonal3Values
(
scale
,
scale
,
scale
),
(
PaintingContext
context
,
Offset
offset
)
{
context
.
paintChild
(
child
,
offset
,
);
},
);
child
=
childAfter
(
child
);
childIndex
+=
1
;
}
}
_textPainter
.
paint
(
canvas
,
offset
);
if
(
_needsClipping
)
{
if
(
_needsClipping
)
{
if
(
_overflowShader
!=
null
)
{
if
(
_overflowShader
!=
null
)
{
canvas
.
translate
(
offset
.
dx
,
offset
.
dy
);
c
ontext
.
c
anvas
.
translate
(
offset
.
dx
,
offset
.
dy
);
final
Paint
paint
=
Paint
()
final
Paint
paint
=
Paint
()
..
blendMode
=
BlendMode
.
modulate
..
blendMode
=
BlendMode
.
modulate
..
shader
=
_overflowShader
;
..
shader
=
_overflowShader
;
canvas
.
drawRect
(
Offset
.
zero
&
size
,
paint
);
c
ontext
.
c
anvas
.
drawRect
(
Offset
.
zero
&
size
,
paint
);
}
}
canvas
.
restore
();
c
ontext
.
c
anvas
.
restore
();
}
}
}
}
...
@@ -481,26 +725,23 @@ class RenderParagraph extends RenderBox {
...
@@ -481,26 +725,23 @@ class RenderParagraph extends RenderBox {
return
_textPainter
.
size
;
return
_textPainter
.
size
;
}
}
final
List
<
int
>
_recognizerOffsets
=
<
int
>[];
// The offsets for each span that requires custom semantics.
final
List
<
GestureRecognizer
>
_recognizers
=
<
GestureRecognizer
>[];
final
List
<
int
>
_inlineSemanticsOffsets
=
<
int
>[];
// Holds either [GestureRecognizer] or null (for placeholders) to generate
// proper semnatics configurations.
final
List
<
dynamic
>
_inlineSemanticsElements
=
<
dynamic
>[];
@override
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
super
.
describeSemanticsConfiguration
(
config
);
_recognizerOffsets
.
clear
();
_inlineSemanticsOffsets
.
clear
();
_recognizers
.
clear
();
_inlineSemanticsElements
.
clear
();
int
offset
=
0
;
final
Accumulator
offset
=
Accumulator
();
text
.
visitTextSpan
((
TextSpan
span
)
{
text
.
visitChildren
((
InlineSpan
span
)
{
if
(
span
.
recognizer
!=
null
&&
(
span
.
recognizer
is
TapGestureRecognizer
||
span
.
recognizer
is
LongPressGestureRecognizer
))
{
span
.
describeSemantics
(
offset
,
_inlineSemanticsOffsets
,
_inlineSemanticsElements
);
final
int
length
=
span
.
semanticsLabel
?.
length
??
span
.
text
.
length
;
_recognizerOffsets
.
add
(
offset
);
_recognizerOffsets
.
add
(
offset
+
length
);
_recognizers
.
add
(
span
.
recognizer
);
}
offset
+=
span
.
text
.
length
;
return
true
;
return
true
;
});
});
if
(
_
recognizer
Offsets
.
isNotEmpty
)
{
if
(
_
inlineSemantics
Offsets
.
isNotEmpty
)
{
config
.
explicitChildNodes
=
true
;
config
.
explicitChildNodes
=
true
;
config
.
isSemanticBoundary
=
true
;
config
.
isSemanticBoundary
=
true
;
}
else
{
}
else
{
...
@@ -511,10 +752,9 @@ class RenderParagraph extends RenderBox {
...
@@ -511,10 +752,9 @@ class RenderParagraph extends RenderBox {
@override
@override
void
assembleSemanticsNode
(
SemanticsNode
node
,
SemanticsConfiguration
config
,
Iterable
<
SemanticsNode
>
children
)
{
void
assembleSemanticsNode
(
SemanticsNode
node
,
SemanticsConfiguration
config
,
Iterable
<
SemanticsNode
>
children
)
{
assert
(
_recognizerOffsets
.
isNotEmpty
);
assert
(
_inlineSemanticsOffsets
.
isNotEmpty
);
assert
(
_recognizerOffsets
.
length
.
isEven
);
assert
(
_inlineSemanticsOffsets
.
length
.
isEven
);
assert
(
_recognizers
.
isNotEmpty
);
assert
(
_inlineSemanticsElements
.
isNotEmpty
);
assert
(
children
.
isEmpty
);
final
List
<
SemanticsNode
>
newChildren
=
<
SemanticsNode
>[];
final
List
<
SemanticsNode
>
newChildren
=
<
SemanticsNode
>[];
final
String
rawLabel
=
text
.
toPlainText
();
final
String
rawLabel
=
text
.
toPlainText
();
int
current
=
0
;
int
current
=
0
;
...
@@ -522,7 +762,7 @@ class RenderParagraph extends RenderBox {
...
@@ -522,7 +762,7 @@ class RenderParagraph extends RenderBox {
TextDirection
currentDirection
=
textDirection
;
TextDirection
currentDirection
=
textDirection
;
Rect
currentRect
;
Rect
currentRect
;
SemanticsConfiguration
buildSemanticsConfig
(
int
start
,
int
end
)
{
SemanticsConfiguration
buildSemanticsConfig
(
int
start
,
int
end
,
{
bool
includeText
=
true
}
)
{
final
TextDirection
initialDirection
=
currentDirection
;
final
TextDirection
initialDirection
=
currentDirection
;
final
TextSelection
selection
=
TextSelection
(
baseOffset:
start
,
extentOffset:
end
);
final
TextSelection
selection
=
TextSelection
(
baseOffset:
start
,
extentOffset:
end
);
final
List
<
ui
.
TextBox
>
rects
=
getBoxesForSelection
(
selection
);
final
List
<
ui
.
TextBox
>
rects
=
getBoxesForSelection
(
selection
);
...
@@ -542,15 +782,21 @@ class RenderParagraph extends RenderBox {
...
@@ -542,15 +782,21 @@ class RenderParagraph extends RenderBox {
rect
.
bottom
.
ceilToDouble
()
+
4.0
,
rect
.
bottom
.
ceilToDouble
()
+
4.0
,
);
);
order
+=
1
;
order
+=
1
;
return
SemanticsConfiguration
()
final
SemanticsConfiguration
configuration
=
SemanticsConfiguration
()
..
sortKey
=
OrdinalSortKey
(
order
)
..
sortKey
=
OrdinalSortKey
(
order
)
..
textDirection
=
initialDirection
..
textDirection
=
initialDirection
;
..
label
=
rawLabel
.
substring
(
start
,
end
);
if
(
includeText
)
{
configuration
.
label
=
rawLabel
.
substring
(
start
,
end
);
}
return
configuration
;
}
}
for
(
int
i
=
0
,
j
=
0
;
i
<
_recognizerOffsets
.
length
;
i
+=
2
,
j
++)
{
int
childIndex
=
0
;
final
int
start
=
_recognizerOffsets
[
i
];
RenderBox
child
=
firstChild
;
final
int
end
=
_recognizerOffsets
[
i
+
1
];
for
(
int
i
=
0
,
j
=
0
;
i
<
_inlineSemanticsOffsets
.
length
;
i
+=
2
,
j
++)
{
final
int
start
=
_inlineSemanticsOffsets
[
i
];
final
int
end
=
_inlineSemanticsOffsets
[
i
+
1
];
// Add semantics for any text between the previous recognizer/widget and this one.
if
(
current
!=
start
)
{
if
(
current
!=
start
)
{
final
SemanticsNode
node
=
SemanticsNode
();
final
SemanticsNode
node
=
SemanticsNode
();
final
SemanticsConfiguration
configuration
=
buildSemanticsConfig
(
current
,
start
);
final
SemanticsConfiguration
configuration
=
buildSemanticsConfig
(
current
,
start
);
...
@@ -558,12 +804,16 @@ class RenderParagraph extends RenderBox {
...
@@ -558,12 +804,16 @@ class RenderParagraph extends RenderBox {
node
.
rect
=
currentRect
;
node
.
rect
=
currentRect
;
newChildren
.
add
(
node
);
newChildren
.
add
(
node
);
}
}
final
dynamic
inlineElement
=
_inlineSemanticsElements
[
j
];
final
SemanticsConfiguration
configuration
=
buildSemanticsConfig
(
start
,
end
,
includeText:
false
);
if
(
inlineElement
!=
null
)
{
// Add semantics for this recognizer.
final
SemanticsNode
node
=
SemanticsNode
();
final
SemanticsNode
node
=
SemanticsNode
();
final
SemanticsConfiguration
configuration
=
buildSemanticsConfig
(
start
,
end
);
if
(
inlineElement
is
TapGestureRecognizer
)
{
final
GestureRecognizer
recognizer
=
_recognizers
[
j
];
final
TapGestureRecognizer
recognizer
=
inlineElement
;
if
(
recognizer
is
TapGestureRecognizer
)
{
configuration
.
onTap
=
recognizer
.
onTap
;
configuration
.
onTap
=
recognizer
.
onTap
;
}
else
if
(
recognizer
is
LongPressGestureRecognizer
)
{
}
else
if
(
inlineElement
is
LongPressGestureRecognizer
)
{
final
LongPressGestureRecognizer
recognizer
=
inlineElement
;
configuration
.
onLongPress
=
recognizer
.
onLongPress
;
configuration
.
onLongPress
=
recognizer
.
onLongPress
;
}
else
{
}
else
{
assert
(
false
);
assert
(
false
);
...
@@ -571,6 +821,21 @@ class RenderParagraph extends RenderBox {
...
@@ -571,6 +821,21 @@ class RenderParagraph extends RenderBox {
node
.
updateWith
(
config:
configuration
);
node
.
updateWith
(
config:
configuration
);
node
.
rect
=
currentRect
;
node
.
rect
=
currentRect
;
newChildren
.
add
(
node
);
newChildren
.
add
(
node
);
}
else
if
(
childIndex
<
children
.
length
)
{
// Add semantics for this placeholder. Semantics are precomputed in the children
// argument.
final
SemanticsNode
childNode
=
children
.
elementAt
(
childIndex
);
final
TextParentData
parentData
=
child
.
parentData
;
childNode
.
rect
=
Rect
.
fromLTWH
(
childNode
.
rect
.
left
,
childNode
.
rect
.
top
,
childNode
.
rect
.
width
*
parentData
.
scale
,
childNode
.
rect
.
height
*
parentData
.
scale
,
);
newChildren
.
add
(
children
.
elementAt
(
childIndex
));
childIndex
+=
1
;
child
=
childAfter
(
child
);
}
current
=
end
;
current
=
end
;
}
}
if
(
current
<
rawLabel
.
length
)
{
if
(
current
<
rawLabel
.
length
)
{
...
...
packages/flutter/lib/src/widgets/basic.dart
View file @
14414f35
...
@@ -12,6 +12,7 @@ import 'package:flutter/services.dart';
...
@@ -12,6 +12,7 @@ import 'package:flutter/services.dart';
import
'debug.dart'
;
import
'debug.dart'
;
import
'framework.dart'
;
import
'framework.dart'
;
import
'localizations.dart'
;
import
'localizations.dart'
;
import
'widget_span.dart'
;
export
'package:flutter/animation.dart'
;
export
'package:flutter/animation.dart'
;
export
'package:flutter/foundation.dart'
show
export
'package:flutter/foundation.dart'
show
...
@@ -4913,7 +4914,9 @@ class Flow extends MultiChildRenderObjectWidget {
...
@@ -4913,7 +4914,9 @@ class Flow extends MultiChildRenderObjectWidget {
/// * [TextSpan], which is used to describe the text in a paragraph.
/// * [TextSpan], which is used to describe the text in a paragraph.
/// * [Text], which automatically applies the ambient styles described by a
/// * [Text], which automatically applies the ambient styles described by a
/// [DefaultTextStyle] to a single string.
/// [DefaultTextStyle] to a single string.
class
RichText
extends
LeafRenderObjectWidget
{
/// * [Text.rich], a const text widget that provides similar functionality
/// as [RichText]. [Text.rich] will inherit [TextStyle] from [DefaultTextStyle].
class
RichText
extends
MultiChildRenderObjectWidget
{
/// Creates a paragraph of rich text.
/// Creates a paragraph of rich text.
///
///
/// The [text], [textAlign], [softWrap], [overflow], and [textScaleFactor]
/// The [text], [textAlign], [softWrap], [overflow], and [textScaleFactor]
...
@@ -4924,7 +4927,7 @@ class RichText extends LeafRenderObjectWidget {
...
@@ -4924,7 +4927,7 @@ class RichText extends LeafRenderObjectWidget {
///
///
/// The [textDirection], if null, defaults to the ambient [Directionality],
/// The [textDirection], if null, defaults to the ambient [Directionality],
/// which in that case must not be null.
/// which in that case must not be null.
const
RichText
({
RichText
({
Key
key
,
Key
key
,
@required
this
.
text
,
@required
this
.
text
,
this
.
textAlign
=
TextAlign
.
start
,
this
.
textAlign
=
TextAlign
.
start
,
...
@@ -4943,10 +4946,23 @@ class RichText extends LeafRenderObjectWidget {
...
@@ -4943,10 +4946,23 @@ class RichText extends LeafRenderObjectWidget {
assert
(
textScaleFactor
!=
null
),
assert
(
textScaleFactor
!=
null
),
assert
(
maxLines
==
null
||
maxLines
>
0
),
assert
(
maxLines
==
null
||
maxLines
>
0
),
assert
(
textWidthBasis
!=
null
),
assert
(
textWidthBasis
!=
null
),
super
(
key:
key
);
super
(
key:
key
,
children:
_extractChildren
(
text
));
// Traverses the InlineSpan tree and depth-first collects the list of
// child widgets that are created in WidgetSpans.
static
List
<
Widget
>
_extractChildren
(
InlineSpan
span
)
{
final
List
<
Widget
>
result
=
<
Widget
>[];
span
.
visitChildren
((
InlineSpan
span
)
{
if
(
span
is
WidgetSpan
)
{
result
.
add
(
span
.
child
);
}
return
true
;
});
return
result
;
}
/// The text to display in this widget.
/// The text to display in this widget.
final
Text
Span
text
;
final
Inline
Span
text
;
/// How the text should be aligned horizontally.
/// How the text should be aligned horizontally.
final
TextAlign
textAlign
;
final
TextAlign
textAlign
;
...
...
packages/flutter/lib/src/widgets/text.dart
View file @
14414f35
...
@@ -256,9 +256,16 @@ class Text extends StatelessWidget {
...
@@ -256,9 +256,16 @@ class Text extends StatelessWidget {
textSpan
=
null
,
textSpan
=
null
,
super
(
key:
key
);
super
(
key:
key
);
/// Creates a text widget with a [TextSpan].
/// Creates a text widget with a [InlineSpan].
///
/// The following subclasses of [InlineSpan] may be used to build rich text:
///
/// * [TextSpan]s define text and children [InlineSpan]s.
/// * [WidgetSpan]s define embedded inline widgets.
///
///
/// The [textSpan] parameter must not be null.
/// The [textSpan] parameter must not be null.
///
/// See [RichText] which provides a lower-level way to draw text.
const
Text
.
rich
(
const
Text
.
rich
(
this
.
textSpan
,
{
this
.
textSpan
,
{
Key
key
,
Key
key
,
...
@@ -285,10 +292,10 @@ class Text extends StatelessWidget {
...
@@ -285,10 +292,10 @@ class Text extends StatelessWidget {
/// This will be null if a [textSpan] is provided instead.
/// This will be null if a [textSpan] is provided instead.
final
String
data
;
final
String
data
;
/// The text to display as a [
Text
Span].
/// The text to display as a [
Inline
Span].
///
///
/// This will be null if [data] is provided instead.
/// This will be null if [data] is provided instead.
final
Text
Span
textSpan
;
final
Inline
Span
textSpan
;
/// If non-null, the style to use for this text.
/// If non-null, the style to use for this text.
///
///
...
...
packages/flutter/lib/src/widgets/widget_inspector.dart
View file @
14414f35
...
@@ -2752,7 +2752,8 @@ class _InspectorOverlayLayer extends Layer {
...
@@ -2752,7 +2752,8 @@ class _InspectorOverlayLayer extends Layer {
)
{
)
{
canvas
.
save
();
canvas
.
save
();
final
double
maxWidth
=
size
.
width
-
2
*
(
_kScreenEdgeMargin
+
_kTooltipPadding
);
final
double
maxWidth
=
size
.
width
-
2
*
(
_kScreenEdgeMargin
+
_kTooltipPadding
);
if
(
_textPainter
==
null
||
_textPainter
.
text
.
text
!=
message
||
_textPainterMaxWidth
!=
maxWidth
)
{
final
TextSpan
textSpan
=
_textPainter
?.
text
;
if
(
_textPainter
==
null
||
textSpan
.
text
!=
message
||
_textPainterMaxWidth
!=
maxWidth
)
{
_textPainterMaxWidth
=
maxWidth
;
_textPainterMaxWidth
=
maxWidth
;
_textPainter
=
TextPainter
()
_textPainter
=
TextPainter
()
..
maxLines
=
_kMaxTooltipLines
..
maxLines
=
_kMaxTooltipLines
...
...
packages/flutter/lib/src/widgets/widget_span.dart
0 → 100644
View file @
14414f35
// Copyright 2015 The Chromium 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:ui'
as
ui
show
ParagraphBuilder
,
PlaceholderAlignment
;
import
'package:flutter/painting.dart'
;
import
'framework.dart'
;
/// An immutable widget that is embedded inline within text.
///
/// The [child] property is the widget that will be embedded. Children are
/// constrained by the width of the paragraph.
///
/// The [child] property may contain its own [Widget] children (if applicable),
/// including [Text] and [RichText] widgets which may include additional
/// [WidgetSpan]s. Child [Text] and [RichText] widgets will be laid out
/// independently and occupy a rectangular space in the parent text layout.
///
/// [WidgetSpan]s will be ignored when passed into a [TextPainter] directly.
/// To properly layout and paint the [child] widget, [WidgetSpan] should be
/// passed into a [Text.rich] widget.
///
/// {@tool sample}
///
/// A card with `Hello World!` embedded inline within a TextSpan tree.
///
/// ```dart
/// Text.rich(
/// TextSpan(
/// children: <InlineSpan>[
/// TextSpan(text: 'Flutter is'),
/// WidgetSpan(
/// child: SizedBox(
/// width: 120,
/// height: 50,
/// child: Card(
/// child: Center(
/// child: Text('Hello World!')
/// )
/// ),
/// )
/// ),
/// TextSpan(text: 'the best!'),
/// ],
/// )
/// )
/// ```
/// {@end-tool}
///
/// [WidgetSpan] contributes the semantics of the [WidgetSpan.child] to the
/// semantics tree.
///
/// See also:
///
/// * [TextSpan], a node that represents text in an [InlineSpan] tree.
/// * [Text], a widget for showing uniformly-styled text.
/// * [RichText], a widget for finer control of text rendering.
/// * [TextPainter], a class for painting [InlineSpan] objects on a [Canvas].
@immutable
class
WidgetSpan
extends
PlaceholderSpan
{
/// Creates a [WidgetSpan] with the given values.
///
/// The [child] property must be non-null. [WidgetSpan] is a leaf node in
/// the [InlineSpan] tree. Child widgets are constrained by the width of the
/// paragraph they occupy. Child widget heights are unconstrained, and may
/// cause the text to overflow and be ellipsized/truncated.
///
/// A [TextStyle] may be provided with the [style] property, but only the
/// decoration, foreground, background, and spacing options will be used.
const
WidgetSpan
({
@required
this
.
child
,
ui
.
PlaceholderAlignment
alignment
=
ui
.
PlaceholderAlignment
.
bottom
,
TextBaseline
baseline
,
TextStyle
style
,
})
:
assert
(
child
!=
null
),
assert
((
identical
(
alignment
,
ui
.
PlaceholderAlignment
.
aboveBaseline
)
||
identical
(
alignment
,
ui
.
PlaceholderAlignment
.
belowBaseline
)
||
identical
(
alignment
,
ui
.
PlaceholderAlignment
.
baseline
))
?
baseline
!=
null
:
true
),
super
(
alignment:
alignment
,
baseline:
baseline
,
style:
style
,
);
/// The widget to embed inline within text.
final
Widget
child
;
/// Adds a placeholder box to the paragraph builder if a size has been
/// calculated for the widget.
///
/// Sizes are provided through `dimensions`, which should contain a 1:1
/// in-order mapping of widget to laid-out dimensions. If no such dimension
/// is provided, the widget will be skipped.
///
/// The `textScaleFactor` will be applied to the laid-out size of the widget.
@override
void
build
(
ui
.
ParagraphBuilder
builder
,
{
double
textScaleFactor
=
1.0
,
@required
List
<
PlaceholderDimensions
>
dimensions
})
{
assert
(
debugAssertIsValid
());
assert
(
dimensions
!=
null
);
final
bool
hasStyle
=
style
!=
null
;
if
(
hasStyle
)
{
builder
.
pushStyle
(
style
.
getTextStyle
(
textScaleFactor:
textScaleFactor
));
}
assert
(
builder
.
placeholderCount
<
dimensions
.
length
);
final
PlaceholderDimensions
currentDimensions
=
dimensions
[
builder
.
placeholderCount
];
builder
.
addPlaceholder
(
currentDimensions
.
size
.
width
,
currentDimensions
.
size
.
height
,
alignment
,
scale:
textScaleFactor
,
baseline:
currentDimensions
.
baseline
,
baselineOffset:
currentDimensions
.
baselineOffset
,
);
if
(
hasStyle
)
{
builder
.
pop
();
}
}
/// Calls `visitor` on this [WidgetSpan]. There are no children spans to walk.
@override
bool
visitChildren
(
InlineSpanVisitor
visitor
)
{
return
visitor
(
this
);
}
@override
InlineSpan
getSpanForPositionVisitor
(
TextPosition
position
,
Accumulator
offset
)
{
return
null
;
}
@override
int
codeUnitAtVisitor
(
int
index
,
Accumulator
offset
)
{
return
null
;
}
@override
RenderComparison
compareTo
(
InlineSpan
other
)
{
if
(
identical
(
this
,
other
))
return
RenderComparison
.
identical
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
RenderComparison
.
layout
;
if
((
style
==
null
)
!=
(
other
.
style
==
null
))
return
RenderComparison
.
layout
;
final
WidgetSpan
typedOther
=
other
;
if
(
child
!=
typedOther
.
child
||
alignment
!=
typedOther
.
alignment
)
{
return
RenderComparison
.
layout
;
}
RenderComparison
result
=
RenderComparison
.
identical
;
if
(
style
!=
null
)
{
final
RenderComparison
candidate
=
style
.
compareTo
(
other
.
style
);
if
(
candidate
.
index
>
result
.
index
)
result
=
candidate
;
if
(
result
==
RenderComparison
.
layout
)
return
result
;
}
return
result
;
}
@override
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
if
(
super
!=
other
)
return
false
;
final
WidgetSpan
typedOther
=
other
;
return
typedOther
.
child
==
child
&&
typedOther
.
alignment
==
alignment
&&
typedOther
.
baseline
==
baseline
;
}
@override
int
get
hashCode
=>
hashValues
(
super
.
hashCode
,
child
,
alignment
,
baseline
);
/// Returns the text span that contains the given position in the text.
@override
InlineSpan
getSpanForPosition
(
TextPosition
position
)
{
assert
(
debugAssertIsValid
());
return
null
;
}
/// In debug mode, throws an exception if the object is not in a
/// valid configuration. Otherwise, returns true.
///
/// This is intended to be used as follows:
///
/// ```dart
/// assert(myWidgetSpan.debugAssertIsValid());
/// ```
@override
bool
debugAssertIsValid
()
{
// WidgetSpans are always valid as asserts prevent invalid WidgetSpans
// from being constructed.
return
true
;
}
}
packages/flutter/lib/widgets.dart
View file @
14414f35
...
@@ -108,4 +108,5 @@ export 'src/widgets/value_listenable_builder.dart';
...
@@ -108,4 +108,5 @@ export 'src/widgets/value_listenable_builder.dart';
export
'src/widgets/viewport.dart'
;
export
'src/widgets/viewport.dart'
;
export
'src/widgets/visibility.dart'
;
export
'src/widgets/visibility.dart'
;
export
'src/widgets/widget_inspector.dart'
;
export
'src/widgets/widget_inspector.dart'
;
export
'src/widgets/widget_span.dart'
;
export
'src/widgets/will_pop_scope.dart'
;
export
'src/widgets/will_pop_scope.dart'
;
packages/flutter/test/painting/text_painter_rtl_test.dart
View file @
14414f35
...
@@ -43,7 +43,8 @@ void main() {
...
@@ -43,7 +43,8 @@ void main() {
// 0 12345678 9 101234567 18 90123456 27
// 0 12345678 9 101234567 18 90123456 27
style:
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
style:
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
);
);
expect
(
painter
.
text
.
text
.
length
,
28
);
TextSpan
textSpan
=
painter
.
text
;
expect
(
textSpan
.
text
.
length
,
28
);
painter
.
layout
();
painter
.
layout
();
// The skips here are because the old rendering code considers the bidi formatting characters
// The skips here are because the old rendering code considers the bidi formatting characters
...
@@ -127,7 +128,8 @@ void main() {
...
@@ -127,7 +128,8 @@ void main() {
);
);
final
List
<
List
<
TextBox
>>
list
=
<
List
<
TextBox
>>[];
final
List
<
List
<
TextBox
>>
list
=
<
List
<
TextBox
>>[];
for
(
int
index
=
0
;
index
<
painter
.
text
.
text
.
length
;
index
+=
1
)
textSpan
=
painter
.
text
;
for
(
int
index
=
0
;
index
<
textSpan
.
text
.
length
;
index
+=
1
)
list
.
add
(
painter
.
getBoxesForSelection
(
TextSelection
(
baseOffset:
index
,
extentOffset:
index
+
1
)));
list
.
add
(
painter
.
getBoxesForSelection
(
TextSelection
(
baseOffset:
index
,
extentOffset:
index
+
1
)));
expect
(
list
,
const
<
List
<
TextBox
>>[
expect
(
list
,
const
<
List
<
TextBox
>>[
<
TextBox
>[],
// U+202E, non-printing Unicode bidi formatting character
<
TextBox
>[],
// U+202E, non-printing Unicode bidi formatting character
...
@@ -172,7 +174,8 @@ void main() {
...
@@ -172,7 +174,8 @@ void main() {
// 0 12345678 9 101234567 18 90123456 27
// 0 12345678 9 101234567 18 90123456 27
style:
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
style:
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
);
);
expect
(
painter
.
text
.
text
.
length
,
28
);
final
TextSpan
textSpan
=
painter
.
text
;
expect
(
textSpan
.
text
.
length
,
28
);
painter
.
layout
();
painter
.
layout
();
final
TextRange
hebrew1
=
painter
.
getWordBoundary
(
const
TextPosition
(
offset:
4
,
affinity:
TextAffinity
.
downstream
));
final
TextRange
hebrew1
=
painter
.
getWordBoundary
(
const
TextPosition
(
offset:
4
,
affinity:
TextAffinity
.
downstream
));
...
@@ -261,7 +264,8 @@ void main() {
...
@@ -261,7 +264,8 @@ void main() {
text:
'A
\
u05D0'
,
// A, Alef
text:
'A
\
u05D0'
,
// A, Alef
style:
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
style:
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
);
);
expect
(
painter
.
text
.
text
.
length
,
2
);
final
TextSpan
textSpan
=
painter
.
text
;
expect
(
textSpan
.
text
.
length
,
2
);
painter
.
layout
(
maxWidth:
10.0
);
painter
.
layout
(
maxWidth:
10.0
);
for
(
int
index
=
0
;
index
<=
2
;
index
+=
1
)
{
for
(
int
index
=
0
;
index
<=
2
;
index
+=
1
)
{
...
...
packages/flutter/test/painting/text_painter_test.dart
View file @
14414f35
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
import
'dart:ui'
as
ui
;
import
'dart:ui'
as
ui
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
void
main
(
)
{
...
@@ -634,4 +635,98 @@ void main() {
...
@@ -634,4 +635,98 @@ void main() {
expect
(
caretOffset
.
dx
,
closeTo
(
0.0
,
0.0001
));
expect
(
caretOffset
.
dx
,
closeTo
(
0.0
,
0.0001
));
expect
(
caretOffset
.
dy
,
closeTo
(
0.0
,
0.0001
));
expect
(
caretOffset
.
dy
,
closeTo
(
0.0
,
0.0001
));
});
});
test
(
'TextPainter widget span'
,
()
{
final
TextPainter
painter
=
TextPainter
()
..
textDirection
=
TextDirection
.
ltr
;
const
String
text
=
'test'
;
painter
.
text
=
const
TextSpan
(
text:
text
,
children:
<
InlineSpan
>[
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
TextSpan
(
text:
text
),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
TextSpan
(
text:
text
),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
WidgetSpan
(
child:
SizedBox
(
width:
50
,
height:
30
)),
]
);
// We provide dimensions for the widgets
painter
.
setPlaceholderDimensions
(
const
<
PlaceholderDimensions
>[
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
51
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
PlaceholderDimensions
(
size:
Size
(
50
,
30
),
baselineOffset:
25
,
alignment:
ui
.
PlaceholderAlignment
.
bottom
),
]);
painter
.
layout
(
maxWidth:
500
);
// Now, each of the WidgetSpans will have their own placeholder 'hole'.
Offset
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
1
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
14
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
4
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
56
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
5
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
106
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
6
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
120
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
10
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
212
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
11
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
262
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
12
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
276
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
13
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
290
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
14
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
304
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
15
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
318
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
16
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
368
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
17
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
418
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
18
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
0
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
19
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
50
);
caretOffset
=
painter
.
getOffsetForCaret
(
const
ui
.
TextPosition
(
offset:
23
),
ui
.
Rect
.
zero
);
expect
(
caretOffset
.
dx
,
250
);
expect
(
painter
.
inlinePlaceholderBoxes
.
length
,
14
);
expect
(
painter
.
inlinePlaceholderBoxes
[
0
],
const
TextBox
.
fromLTRBD
(
56
,
0
,
106
,
30
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
2
],
const
TextBox
.
fromLTRBD
(
212
,
0
,
262
,
30
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
3
],
const
TextBox
.
fromLTRBD
(
318
,
0
,
368
,
30
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
4
],
const
TextBox
.
fromLTRBD
(
368
,
0
,
418
,
30
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
5
],
const
TextBox
.
fromLTRBD
(
418
,
0
,
468
,
30
,
TextDirection
.
ltr
));
// line should break here
expect
(
painter
.
inlinePlaceholderBoxes
[
6
],
const
TextBox
.
fromLTRBD
(
0
,
30
,
50
,
60
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
7
],
const
TextBox
.
fromLTRBD
(
50
,
30
,
100
,
60
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
10
],
const
TextBox
.
fromLTRBD
(
200
,
30
,
250
,
60
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
11
],
const
TextBox
.
fromLTRBD
(
250
,
30
,
300
,
60
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
12
],
const
TextBox
.
fromLTRBD
(
300
,
30
,
351
,
60
,
TextDirection
.
ltr
));
expect
(
painter
.
inlinePlaceholderBoxes
[
13
],
const
TextBox
.
fromLTRBD
(
351
,
30
,
401
,
60
,
TextDirection
.
ltr
));
});
}
}
packages/flutter/test/painting/text_span_test.dart
View file @
14414f35
...
@@ -3,17 +3,17 @@
...
@@ -3,17 +3,17 @@
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter/painting.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter
_test/flutter_test.dart'
show
nonconst
;
import
'package:flutter
/widgets.dart'
;
import
'../flutter_test_alternative.dart'
;
import
'../flutter_test_alternative.dart'
;
void
main
(
)
{
void
main
(
)
{
test
(
'TextSpan equals'
,
()
{
test
(
'TextSpan equals'
,
()
{
final
TextSpan
a1
=
TextSpan
(
text:
nonconst
(
'a'
)
);
const
TextSpan
a1
=
TextSpan
(
text:
'a'
);
final
TextSpan
a2
=
TextSpan
(
text:
nonconst
(
'a'
)
);
const
TextSpan
a2
=
TextSpan
(
text:
'a'
);
final
TextSpan
b1
=
TextSpan
(
children:
<
TextSpan
>[
a1
]);
const
TextSpan
b1
=
TextSpan
(
children:
<
TextSpan
>[
a1
]);
final
TextSpan
b2
=
TextSpan
(
children:
<
TextSpan
>[
a2
]);
const
TextSpan
b2
=
TextSpan
(
children:
<
TextSpan
>[
a2
]);
final
TextSpan
c1
=
TextSpan
(
text:
nonconst
(
null
)
);
const
TextSpan
c1
=
TextSpan
(
text:
null
);
final
TextSpan
c2
=
TextSpan
(
text:
nonconst
(
null
)
);
const
TextSpan
c2
=
TextSpan
(
text:
null
);
expect
(
a1
==
a2
,
isTrue
);
expect
(
a1
==
a2
,
isTrue
);
expect
(
b1
==
b2
,
isTrue
);
expect
(
b1
==
b2
,
isTrue
);
...
@@ -73,6 +73,18 @@ void main() {
...
@@ -73,6 +73,18 @@ void main() {
expect
(
textSpan
.
toPlainText
(),
'abc'
);
expect
(
textSpan
.
toPlainText
(),
'abc'
);
});
});
test
(
'WidgetSpan toPlainText'
,
()
{
const
TextSpan
textSpan
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
SizedBox
(
width:
10
,
height:
10
)),
TextSpan
(
text:
'c'
),
],
);
expect
(
textSpan
.
toPlainText
(),
'ab
\
uFFFCc'
);
});
test
(
'TextSpan toPlainText with semanticsLabel'
,
()
{
test
(
'TextSpan toPlainText with semanticsLabel'
,
()
{
const
TextSpan
textSpan
=
TextSpan
(
const
TextSpan
textSpan
=
TextSpan
(
text:
'a'
,
text:
'a'
,
...
@@ -84,4 +96,117 @@ void main() {
...
@@ -84,4 +96,117 @@ void main() {
expect
(
textSpan
.
toPlainText
(),
'afooc'
);
expect
(
textSpan
.
toPlainText
(),
'afooc'
);
expect
(
textSpan
.
toPlainText
(
includeSemanticsLabels:
false
),
'abc'
);
expect
(
textSpan
.
toPlainText
(
includeSemanticsLabels:
false
),
'abc'
);
});
});
test
(
'TextSpan widget change test'
,
()
{
const
TextSpan
textSpan1
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
SizedBox
(
width:
10
,
height:
10
)),
TextSpan
(
text:
'c'
),
],
);
const
TextSpan
textSpan2
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
SizedBox
(
width:
10
,
height:
10
)),
TextSpan
(
text:
'c'
),
],
);
const
TextSpan
textSpan3
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
SizedBox
(
width:
11
,
height:
10
)),
TextSpan
(
text:
'c'
),
],
);
const
TextSpan
textSpan4
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
Text
(
'test'
)),
TextSpan
(
text:
'c'
),
],
);
const
TextSpan
textSpan5
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
Text
(
'different!'
)),
TextSpan
(
text:
'c'
),
],
);
const
TextSpan
textSpan6
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
SizedBox
(
width:
10
,
height:
10
),
alignment:
PlaceholderAlignment
.
top
,
),
TextSpan
(
text:
'c'
),
],
);
expect
(
textSpan1
.
compareTo
(
textSpan3
),
RenderComparison
.
layout
);
expect
(
textSpan1
.
compareTo
(
textSpan4
),
RenderComparison
.
layout
);
expect
(
textSpan1
.
compareTo
(
textSpan1
),
RenderComparison
.
identical
);
expect
(
textSpan2
.
compareTo
(
textSpan2
),
RenderComparison
.
identical
);
expect
(
textSpan3
.
compareTo
(
textSpan3
),
RenderComparison
.
identical
);
expect
(
textSpan2
.
compareTo
(
textSpan3
),
RenderComparison
.
layout
);
expect
(
textSpan4
.
compareTo
(
textSpan5
),
RenderComparison
.
layout
);
expect
(
textSpan3
.
compareTo
(
textSpan5
),
RenderComparison
.
layout
);
expect
(
textSpan2
.
compareTo
(
textSpan5
),
RenderComparison
.
layout
);
expect
(
textSpan1
.
compareTo
(
textSpan5
),
RenderComparison
.
layout
);
expect
(
textSpan1
.
compareTo
(
textSpan6
),
RenderComparison
.
layout
);
});
test
(
'TextSpan nested widget change test'
,
()
{
const
TextSpan
textSpan1
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
Text
.
rich
(
TextSpan
(
children:
<
InlineSpan
>[
WidgetSpan
(
child:
SizedBox
(
width:
10
,
height:
10
)),
TextSpan
(
text:
'The sky is falling :)'
)
],
)
),
),
TextSpan
(
text:
'c'
),
],
);
const
TextSpan
textSpan2
=
TextSpan
(
text:
'a'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'b'
),
WidgetSpan
(
child:
Text
.
rich
(
TextSpan
(
children:
<
InlineSpan
>[
WidgetSpan
(
child:
SizedBox
(
width:
10
,
height:
11
)),
TextSpan
(
text:
'The sky is falling :)'
)
],
)
),
),
TextSpan
(
text:
'c'
),
],
);
expect
(
textSpan1
.
compareTo
(
textSpan2
),
RenderComparison
.
layout
);
expect
(
textSpan1
.
compareTo
(
textSpan1
),
RenderComparison
.
identical
);
expect
(
textSpan2
.
compareTo
(
textSpan2
),
RenderComparison
.
identical
);
});
}
}
packages/flutter/test/rendering/paragraph_test.dart
View file @
14414f35
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
import
'dart:ui'
as
ui
show
TextBox
;
import
'dart:ui'
as
ui
show
TextBox
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
@@ -324,4 +325,93 @@ void main() {
...
@@ -324,4 +325,93 @@ void main() {
expect
(
paragraph
.
locale
,
const
Locale
(
'ja'
,
'JP'
));
expect
(
paragraph
.
locale
,
const
Locale
(
'ja'
,
'JP'
));
});
});
test
(
'inline widgets test'
,
()
{
const
TextSpan
text
=
TextSpan
(
text:
'a'
,
style:
TextStyle
(
fontSize:
10.0
),
children:
<
InlineSpan
>[
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
TextSpan
(
text:
'a'
),
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
],
);
// Fake the render boxes that correspond to the WidgetSpans. We use
// RenderParagraph to reduce dependencies this test has.
final
List
<
RenderBox
>
renderBoxes
=
<
RenderBox
>[];
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
final
RenderParagraph
paragraph
=
RenderParagraph
(
text
,
textDirection:
TextDirection
.
ltr
,
children:
renderBoxes
,
);
layout
(
paragraph
,
constraints:
const
BoxConstraints
(
maxWidth:
100.0
));
final
List
<
ui
.
TextBox
>
boxes
=
paragraph
.
getBoxesForSelection
(
const
TextSelection
(
baseOffset:
0
,
extentOffset:
8
)
);
expect
(
boxes
.
length
,
equals
(
5
));
expect
(
boxes
[
0
],
const
TextBox
.
fromLTRBD
(
0.0
,
4.0
,
10.0
,
14.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
1
],
const
TextBox
.
fromLTRBD
(
10.0
,
0.0
,
24.0
,
14.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
2
],
const
TextBox
.
fromLTRBD
(
24.0
,
0.0
,
38.0
,
14.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
3
],
const
TextBox
.
fromLTRBD
(
38.0
,
4.0
,
48.0
,
14.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
4
],
const
TextBox
.
fromLTRBD
(
48.0
,
0.0
,
62.0
,
14.0
,
TextDirection
.
ltr
));
// Ahem-based tests don't yet quite work on Windows or some MacOS environments
},
skip:
isWindows
||
isMacOS
);
test
(
'inline widgets multiline test'
,
()
{
const
TextSpan
text
=
TextSpan
(
text:
'a'
,
style:
TextStyle
(
fontSize:
10.0
),
children:
<
InlineSpan
>[
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
TextSpan
(
text:
'a'
),
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
WidgetSpan
(
child:
SizedBox
(
width:
21
,
height:
21
)),
],
);
// Fake the render boxes that correspond to the WidgetSpans. We use
// RenderParagraph to reduce dependencies this test has.
final
List
<
RenderBox
>
renderBoxes
=
<
RenderBox
>[];
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
renderBoxes
.
add
(
RenderParagraph
(
const
TextSpan
(
text:
'b'
),
textDirection:
TextDirection
.
ltr
));
final
RenderParagraph
paragraph
=
RenderParagraph
(
text
,
textDirection:
TextDirection
.
ltr
,
children:
renderBoxes
,
);
layout
(
paragraph
,
constraints:
const
BoxConstraints
(
maxWidth:
50.0
));
final
List
<
ui
.
TextBox
>
boxes
=
paragraph
.
getBoxesForSelection
(
const
TextSelection
(
baseOffset:
0
,
extentOffset:
12
)
);
expect
(
boxes
.
length
,
equals
(
9
));
expect
(
boxes
[
0
],
const
TextBox
.
fromLTRBD
(
0.0
,
4.0
,
10.0
,
14.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
1
],
const
TextBox
.
fromLTRBD
(
10.0
,
0.0
,
24.0
,
14.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
2
],
const
TextBox
.
fromLTRBD
(
24.0
,
0.0
,
38.0
,
14.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
3
],
const
TextBox
.
fromLTRBD
(
38.0
,
4.0
,
48.0
,
14.0
,
TextDirection
.
ltr
));
// Wraps
expect
(
boxes
[
4
],
const
TextBox
.
fromLTRBD
(
0.0
,
14.0
,
14.0
,
28.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
5
],
const
TextBox
.
fromLTRBD
(
14.0
,
14.0
,
28.0
,
28.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
6
],
const
TextBox
.
fromLTRBD
(
28.0
,
14.0
,
42.0
,
28.0
,
TextDirection
.
ltr
));
// Wraps
expect
(
boxes
[
7
],
const
TextBox
.
fromLTRBD
(
0.0
,
28.0
,
14.0
,
42.0
,
TextDirection
.
ltr
));
expect
(
boxes
[
8
],
const
TextBox
.
fromLTRBD
(
14.0
,
28.0
,
28.0
,
42.0
,
TextDirection
.
ltr
));
// Ahem-based tests don't yet quite work on Windows or some MacOS environments
},
skip:
isWindows
||
isMacOS
);
}
}
packages/flutter/test/widgets/backdrop_filter_test.dart
View file @
14414f35
...
@@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
...
@@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
void
main
(
)
{
void
main
(
)
{
testWidgets
(
'BackdropFilter
\'
s cull rect does not shrink'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'BackdropFilter
\'
s cull rect does not shrink'
,
(
WidgetTester
tester
)
async
{
tester
.
binding
.
addTime
(
const
Duration
(
seconds:
15
));
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
MaterialApp
(
MaterialApp
(
home:
Scaffold
(
home:
Scaffold
(
...
...
packages/flutter/test/widgets/basic_test.dart
View file @
14414f35
...
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
...
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/rendering.dart'
;
void
main
(
)
{
void
main
(
)
{
group
(
'PhysicalShape'
,
()
{
group
(
'PhysicalShape'
,
()
{
testWidgets
(
'properties'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'properties'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
14414f35
...
@@ -1870,8 +1870,9 @@ void main() {
...
@@ -1870,8 +1870,9 @@ void main() {
final
RenderEditable
renderEditable
=
findRenderEditable
(
tester
);
final
RenderEditable
renderEditable
=
findRenderEditable
(
tester
);
// The actual text span is split into 3 parts with the middle part underlined.
// The actual text span is split into 3 parts with the middle part underlined.
expect
(
renderEditable
.
text
.
children
.
length
,
3
);
expect
(
renderEditable
.
text
.
children
.
length
,
3
);
expect
(
renderEditable
.
text
.
children
[
1
].
text
,
'composing'
);
final
TextSpan
textSpan
=
renderEditable
.
text
.
children
[
1
];
expect
(
renderEditable
.
text
.
children
[
1
].
style
.
decoration
,
TextDecoration
.
underline
);
expect
(
textSpan
.
text
,
'composing'
);
expect
(
textSpan
.
style
.
decoration
,
TextDecoration
.
underline
);
focusNode
.
unfocus
();
focusNode
.
unfocus
();
await
tester
.
pump
();
await
tester
.
pump
();
...
...
packages/flutter/test/widgets/text_golden_test.dart
View file @
14414f35
...
@@ -151,16 +151,15 @@ void main() {
...
@@ -151,16 +151,15 @@ void main() {
decoration:
const
BoxDecoration
(
decoration:
const
BoxDecoration
(
color:
Colors
.
green
,
color:
Colors
.
green
,
),
),
child:
RichText
(
child:
Text
.
rich
(
textDirection:
TextDirection
.
ltr
,
TextSpan
(
text:
TextSpan
(
text:
'text1 '
,
text:
'text1 '
,
style:
TextStyle
(
style:
TextStyle
(
color:
translucentGreen
,
color:
translucentGreen
,
background:
Paint
()
background:
Paint
()
..
color
=
red
.
withOpacity
(
0.5
),
..
color
=
red
.
withOpacity
(
0.5
),
),
),
children:
<
Text
Span
>[
children:
<
Inline
Span
>[
TextSpan
(
TextSpan
(
text:
'text2'
,
text:
'text2'
,
style:
TextStyle
(
style:
TextStyle
(
...
@@ -171,6 +170,7 @@ void main() {
...
@@ -171,6 +170,7 @@ void main() {
),
),
],
],
),
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
...
@@ -242,7 +242,7 @@ void main() {
...
@@ -242,7 +242,7 @@ void main() {
find
.
byType
(
Container
),
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.StrutDefault.png'
),
matchesGoldenFile
(
'text_golden.StrutDefault.png'
),
);
);
},
skip:
true
);
// Should only be on linux (skip: !
Platform.
isLinux).
},
skip:
true
);
// Should only be on linux (skip: !isLinux).
// Disabled for now until font inconsistency is resolved.
// Disabled for now until font inconsistency is resolved.
testWidgets
(
'Strut text 1'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Strut text 1'
,
(
WidgetTester
tester
)
async
{
...
@@ -270,7 +270,7 @@ void main() {
...
@@ -270,7 +270,7 @@ void main() {
find
.
byType
(
Container
),
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Strut.1.1.png'
),
matchesGoldenFile
(
'text_golden.Strut.1.1.png'
),
);
);
},
skip:
true
);
// Should only be on linux (skip: !
Platform.
isLinux).
},
skip:
true
);
// Should only be on linux (skip: !isLinux).
// Disabled for now until font inconsistency is resolved.
// Disabled for now until font inconsistency is resolved.
testWidgets
(
'Strut text 2'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Strut text 2'
,
(
WidgetTester
tester
)
async
{
...
@@ -299,7 +299,7 @@ void main() {
...
@@ -299,7 +299,7 @@ void main() {
find
.
byType
(
Container
),
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Strut.2.1.png'
),
matchesGoldenFile
(
'text_golden.Strut.2.1.png'
),
);
);
},
skip:
true
);
// Should only be on linux (skip: !
Platform.
isLinux).
},
skip:
true
);
// Should only be on linux (skip: !isLinux).
// Disabled for now until font inconsistency is resolved.
// Disabled for now until font inconsistency is resolved.
testWidgets
(
'Strut text rich'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Strut text rich'
,
(
WidgetTester
tester
)
async
{
...
@@ -319,7 +319,7 @@ void main() {
...
@@ -319,7 +319,7 @@ void main() {
color:
Colors
.
red
,
color:
Colors
.
red
,
fontSize:
30
,
fontSize:
30
,
),
),
children:
<
Text
Span
>[
children:
<
Inline
Span
>[
TextSpan
(
TextSpan
(
text:
'Second line!
\n
'
,
text:
'Second line!
\n
'
,
style:
TextStyle
(
style:
TextStyle
(
...
@@ -351,7 +351,7 @@ void main() {
...
@@ -351,7 +351,7 @@ void main() {
find
.
byType
(
Container
),
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Strut.3.1.png'
),
matchesGoldenFile
(
'text_golden.Strut.3.1.png'
),
);
);
},
skip:
true
);
// Should only be on linux (skip: !
Platform.
isLinux).
},
skip:
true
);
// Should only be on linux (skip: !isLinux).
// Disabled for now until font inconsistency is resolved.
// Disabled for now until font inconsistency is resolved.
testWidgets
(
'Strut text font fallback'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Strut text font fallback'
,
(
WidgetTester
tester
)
async
{
...
@@ -387,7 +387,7 @@ void main() {
...
@@ -387,7 +387,7 @@ void main() {
find
.
byType
(
Container
),
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.Strut.4.1.png'
),
matchesGoldenFile
(
'text_golden.Strut.4.1.png'
),
);
);
},
skip:
true
);
// Should only be on linux (skip: !
Platform.
isLinux).
},
skip:
true
);
// Should only be on linux (skip: !isLinux).
// Disabled for now until font inconsistency is resolved.
// Disabled for now until font inconsistency is resolved.
testWidgets
(
'Strut text rich forceStrutHeight'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Strut text rich forceStrutHeight'
,
(
WidgetTester
tester
)
async
{
...
@@ -407,7 +407,7 @@ void main() {
...
@@ -407,7 +407,7 @@ void main() {
color:
Colors
.
red
,
color:
Colors
.
red
,
fontSize:
30
,
fontSize:
30
,
),
),
children:
<
Text
Span
>[
children:
<
Inline
Span
>[
TextSpan
(
TextSpan
(
text:
'Second line!
\n
'
,
text:
'Second line!
\n
'
,
style:
TextStyle
(
style:
TextStyle
(
...
@@ -439,7 +439,7 @@ void main() {
...
@@ -439,7 +439,7 @@ void main() {
find
.
byType
(
Container
),
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.StrutForce.1.1.png'
),
matchesGoldenFile
(
'text_golden.StrutForce.1.1.png'
),
);
);
},
skip:
true
);
// Should only be on linux (skip: !
Platform.
isLinux).
},
skip:
true
);
// Should only be on linux (skip: !isLinux).
// Disabled for now until font inconsistency is resolved.
// Disabled for now until font inconsistency is resolved.
testWidgets
(
'Decoration thickness'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Decoration thickness'
,
(
WidgetTester
tester
)
async
{
...
@@ -518,4 +518,807 @@ void main() {
...
@@ -518,4 +518,807 @@ void main() {
matchesGoldenFile
(
'text_golden.DecorationThickness.1.0.png'
),
matchesGoldenFile
(
'text_golden.DecorationThickness.1.0.png'
),
);
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
testWidgets
(
'Text Inline widget'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Center
(
child:
RepaintBoundary
(
child:
Material
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Container
(
width:
400.0
,
height:
200.0
,
decoration:
const
BoxDecoration
(
color:
Color
(
0xff00ff00
),
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
0
,
maxWidth:
200
,
minHeight:
0
,
maxHeight:
100
),
child:
const
Text
.
rich
(
TextSpan
(
text:
'C '
,
style:
TextStyle
(
fontSize:
16
,
),
children:
<
InlineSpan
>[
WidgetSpan
(
child:
Checkbox
(
value:
true
,
onChanged:
null
),
),
WidgetSpan
(
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
TextSpan
(
text:
'He '
,
style:
TextStyle
(
fontSize:
20
)),
WidgetSpan
(
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffffff00
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffff0000
),
),
)
),
),
)
),
),
TextSpan
(
text:
'hello world! sieze the day!'
),
WidgetSpan
(
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
child:
Checkbox
(
value:
false
,
onChanged:
null
),
alignment:
PlaceholderAlignment
.
baseline
,
baseline:
TextBaseline
.
alphabetic
),
WidgetSpan
(
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
child:
Text
(
'embedded'
),
),
],
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
),
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidget.1.1.png'
),
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
testWidgets
(
'Text Inline widget textfield'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Center
(
child:
MaterialApp
(
home:
RepaintBoundary
(
child:
Material
(
child:
Container
(
width:
400.0
,
height:
200.0
,
decoration:
const
BoxDecoration
(
color:
Color
(
0xff00ff00
),
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
0
,
maxWidth:
200
,
minHeight:
0
,
maxHeight:
100
),
child:
const
Text
.
rich
(
TextSpan
(
text:
'My name is: '
,
style:
TextStyle
(
fontSize:
20
,
),
children:
<
InlineSpan
>[
WidgetSpan
(
child:
SizedBox
(
width:
70
,
height:
25
,
child:
TextField
()),
),
TextSpan
(
text:
', and my favorite city is: '
,
style:
TextStyle
(
fontSize:
20
)),
WidgetSpan
(
child:
SizedBox
(
width:
70
,
height:
25
,
child:
TextField
()),
),
],
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
),
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidget.2.2.png'
),
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
// This tests if multiple Text.rich widgets are able to inline nest within each other.
testWidgets
(
'Text Inline widget nesting'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Center
(
child:
MaterialApp
(
home:
RepaintBoundary
(
child:
Material
(
child:
Container
(
width:
400.0
,
height:
200.0
,
decoration:
const
BoxDecoration
(
color:
Color
(
0xff00ff00
),
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
0
,
maxWidth:
200
,
minHeight:
0
,
maxHeight:
100
),
child:
const
Text
.
rich
(
TextSpan
(
text:
'outer'
,
style:
TextStyle
(
fontSize:
20
,
),
children:
<
InlineSpan
>[
WidgetSpan
(
child:
Text
.
rich
(
TextSpan
(
text:
'inner'
,
style:
TextStyle
(
color:
Color
(
0xff402f4ff
)),
children:
<
InlineSpan
>[
WidgetSpan
(
child:
Text
.
rich
(
TextSpan
(
text:
'inner2'
,
style:
TextStyle
(
color:
Color
(
0xff003ffff
)),
children:
<
InlineSpan
>[
WidgetSpan
(
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffffff30
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xff5f00f0
),
),
)
),
),
)
),
),
],
),
),
),
WidgetSpan
(
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xff5fff00
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xff5f0000
),
),
)
),
),
)
),
),
],
),
),
),
TextSpan
(
text:
'outer'
,
style:
TextStyle
(
fontSize:
20
)),
WidgetSpan
(
child:
SizedBox
(
width:
70
,
height:
25
,
child:
TextField
()),
),
WidgetSpan
(
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffff00ff
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xff0000ff
),
),
)
),
),
)
),
),
],
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
),
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetNest.1.2.png'
),
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
testWidgets
(
'Text Inline widget baseline'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Center
(
child:
RepaintBoundary
(
child:
Material
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Container
(
width:
400.0
,
height:
200.0
,
decoration:
const
BoxDecoration
(
color:
Color
(
0xff00ff00
),
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
0
,
maxWidth:
200
,
minHeight:
0
,
maxHeight:
100
),
child:
const
Text
.
rich
(
TextSpan
(
text:
'C '
,
style:
TextStyle
(
fontSize:
16
,
),
children:
<
InlineSpan
>[
WidgetSpan
(
alignment:
PlaceholderAlignment
.
baseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
),
WidgetSpan
(
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
TextSpan
(
text:
'He '
,
style:
TextStyle
(
fontSize:
20
)),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
baseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffffff00
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffff0000
),
),
)
),
),
)
),
),
TextSpan
(
text:
'hello world! sieze the day!'
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
baseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
baseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
baseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
baseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
baseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Text
(
'embedded'
),
),
TextSpan
(
text:
'ref'
),
],
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
),
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetBaseline.1.1.png'
),
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
testWidgets
(
'Text Inline widget aboveBaseline'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Center
(
child:
RepaintBoundary
(
child:
Material
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Container
(
width:
400.0
,
height:
200.0
,
decoration:
const
BoxDecoration
(
color:
Color
(
0xff00ff00
),
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
0
,
maxWidth:
200
,
minHeight:
0
,
maxHeight:
100
),
child:
const
Text
.
rich
(
TextSpan
(
text:
'C '
,
style:
TextStyle
(
fontSize:
16
,
),
children:
<
InlineSpan
>[
WidgetSpan
(
alignment:
PlaceholderAlignment
.
aboveBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
),
WidgetSpan
(
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
TextSpan
(
text:
'He '
,
style:
TextStyle
(
fontSize:
20
)),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
aboveBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffffff00
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffff0000
),
),
)
),
),
)
),
),
TextSpan
(
text:
'hello world! sieze the day!'
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
aboveBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
aboveBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
aboveBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
aboveBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
aboveBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Text
(
'embedded'
),
),
TextSpan
(
text:
'ref'
),
],
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
),
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetAboveBaseline.1.1.png'
),
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
testWidgets
(
'Text Inline widget belowBaseline'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Center
(
child:
RepaintBoundary
(
child:
Material
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Container
(
width:
400.0
,
height:
200.0
,
decoration:
const
BoxDecoration
(
color:
Color
(
0xff00ff00
),
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
0
,
maxWidth:
200
,
minHeight:
0
,
maxHeight:
100
),
child:
const
Text
.
rich
(
TextSpan
(
text:
'C '
,
style:
TextStyle
(
fontSize:
16
,
),
children:
<
InlineSpan
>[
WidgetSpan
(
alignment:
PlaceholderAlignment
.
belowBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
),
WidgetSpan
(
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
TextSpan
(
text:
'He '
,
style:
TextStyle
(
fontSize:
20
)),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
belowBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffffff00
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffff0000
),
),
)
),
),
)
),
),
TextSpan
(
text:
'hello world! sieze the day!'
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
belowBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
belowBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
belowBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
belowBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
belowBaseline
,
baseline:
TextBaseline
.
alphabetic
,
child:
Text
(
'embedded'
),
),
TextSpan
(
text:
'ref'
),
],
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
),
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetBelowBaseline.1.1.png'
),
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
testWidgets
(
'Text Inline widget top'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Center
(
child:
RepaintBoundary
(
child:
Material
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Container
(
width:
400.0
,
height:
200.0
,
decoration:
const
BoxDecoration
(
color:
Color
(
0xff00ff00
),
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
0
,
maxWidth:
200
,
minHeight:
0
,
maxHeight:
100
),
child:
const
Text
.
rich
(
TextSpan
(
text:
'C '
,
style:
TextStyle
(
fontSize:
16
,
),
children:
<
InlineSpan
>[
WidgetSpan
(
alignment:
PlaceholderAlignment
.
top
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
),
WidgetSpan
(
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
TextSpan
(
text:
'He '
,
style:
TextStyle
(
fontSize:
20
)),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
top
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffffff00
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffff0000
),
),
)
),
),
)
),
),
TextSpan
(
text:
'hello world! sieze the day!'
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
top
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
top
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
top
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
top
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
top
,
baseline:
TextBaseline
.
alphabetic
,
child:
Text
(
'embedded'
),
),
TextSpan
(
text:
'ref'
),
],
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
),
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetTop.1.1.png'
),
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
testWidgets
(
'Text Inline widget middle'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Center
(
child:
RepaintBoundary
(
child:
Material
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Container
(
width:
400.0
,
height:
200.0
,
decoration:
const
BoxDecoration
(
color:
Color
(
0xff00ff00
),
),
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
0
,
maxWidth:
200
,
minHeight:
0
,
maxHeight:
100
),
child:
const
Text
.
rich
(
TextSpan
(
text:
'C '
,
style:
TextStyle
(
fontSize:
16
,
),
children:
<
InlineSpan
>[
WidgetSpan
(
alignment:
PlaceholderAlignment
.
middle
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
),
WidgetSpan
(
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
TextSpan
(
text:
'He '
,
style:
TextStyle
(
fontSize:
20
)),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
middle
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
50.0
,
height:
55.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffffff00
),
),
child:
Center
(
child:
SizedBox
(
width:
10.0
,
height:
15.0
,
child:
DecoratedBox
(
decoration:
BoxDecoration
(
color:
Color
(
0xffff0000
),
),
)
),
),
)
),
),
TextSpan
(
text:
'hello world! sieze the day!'
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
middle
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
middle
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
middle
,
baseline:
TextBaseline
.
alphabetic
,
child:
Checkbox
(
value:
false
,
onChanged:
null
),
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
middle
,
baseline:
TextBaseline
.
alphabetic
,
child:
SizedBox
(
width:
20
,
height:
20
,
child:
Checkbox
(
value:
true
,
onChanged:
null
),
)
),
WidgetSpan
(
alignment:
PlaceholderAlignment
.
middle
,
baseline:
TextBaseline
.
alphabetic
,
child:
Text
(
'embedded'
),
),
TextSpan
(
text:
'ref'
),
],
),
textDirection:
TextDirection
.
ltr
,
),
),
),
),
),
),
),
);
await
expectLater
(
find
.
byType
(
Container
),
matchesGoldenFile
(
'text_golden.TextInlineWidgetMiddle.1.1.png'
),
);
},
skip:
!
isLinux
);
// Coretext uses different thicknesses for decoration
}
}
packages/flutter/test/widgets/text_test.dart
View file @
14414f35
...
@@ -2,11 +2,11 @@
...
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter/gestures.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/widgets.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'semantics_tester.dart'
;
import
'semantics_tester.dart'
;
...
@@ -294,6 +294,140 @@ void main() {
...
@@ -294,6 +294,140 @@ void main() {
semantics
.
dispose
();
semantics
.
dispose
();
},
skip:
true
);
// TODO(jonahwilliams): correct once https://github.com/flutter/flutter/issues/20891 is resolved.
},
skip:
true
);
// TODO(jonahwilliams): correct once https://github.com/flutter/flutter/issues/20891 is resolved.
testWidgets
(
'inline widgets generate semantic nodes'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
const
TextStyle
textStyle
=
TextStyle
(
fontFamily:
'Ahem'
);
await
tester
.
pumpWidget
(
Text
.
rich
(
TextSpan
(
children:
<
InlineSpan
>[
const
TextSpan
(
text:
'a '
),
TextSpan
(
text:
'pebble'
,
recognizer:
TapGestureRecognizer
()..
onTap
=
()
{
}),
const
TextSpan
(
text:
' in the '
),
WidgetSpan
(
child:
SizedBox
(
width:
20
,
height:
40
,
child:
Card
(
child:
RichText
(
text:
const
TextSpan
(
text:
'INTERRUPTION'
),
textDirection:
TextDirection
.
rtl
,
),
),
),
),
const
TextSpan
(
text:
'sky'
),
],
style:
textStyle
,
),
textDirection:
TextDirection
.
ltr
,
),
);
final
TestSemantics
expectedSemantics
=
TestSemantics
.
root
(
children:
<
TestSemantics
>[
TestSemantics
.
rootChild
(
children:
<
TestSemantics
>[
TestSemantics
(
label:
'a '
,
textDirection:
TextDirection
.
ltr
,
),
TestSemantics
(
label:
'pebble'
,
textDirection:
TextDirection
.
ltr
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
TestSemantics
(
label:
' in the '
,
textDirection:
TextDirection
.
ltr
,
),
TestSemantics
(
label:
'INTERRUPTION'
,
textDirection:
TextDirection
.
rtl
,
),
TestSemantics
(
label:
'sky'
,
textDirection:
TextDirection
.
ltr
,
),
],
),
],
);
expect
(
semantics
,
hasSemantics
(
expectedSemantics
,
ignoreTransform:
true
,
ignoreId:
true
,
ignoreRect:
true
));
semantics
.
dispose
();
});
testWidgets
(
'inline widgets semantic nodes scale'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
const
TextStyle
textStyle
=
TextStyle
(
fontFamily:
'Ahem'
);
await
tester
.
pumpWidget
(
Text
.
rich
(
TextSpan
(
children:
<
InlineSpan
>[
const
TextSpan
(
text:
'a '
),
TextSpan
(
text:
'pebble'
,
recognizer:
TapGestureRecognizer
()..
onTap
=
()
{
}),
const
TextSpan
(
text:
' in the '
),
WidgetSpan
(
child:
SizedBox
(
width:
20
,
height:
40
,
child:
Card
(
child:
RichText
(
text:
const
TextSpan
(
text:
'INTERRUPTION'
),
textDirection:
TextDirection
.
rtl
,
),
),
),
),
const
TextSpan
(
text:
'sky'
),
],
style:
textStyle
,
),
textDirection:
TextDirection
.
ltr
,
textScaleFactor:
2
,
),
);
final
TestSemantics
expectedSemantics
=
TestSemantics
.
root
(
children:
<
TestSemantics
>[
TestSemantics
.
rootChild
(
rect:
const
Rect
.
fromLTRB
(
0.0
,
0.0
,
800.0
,
600.0
),
children:
<
TestSemantics
>[
TestSemantics
(
label:
'a '
,
textDirection:
TextDirection
.
ltr
,
rect:
const
Rect
.
fromLTRB
(-
4.0
,
48.0
,
60.0
,
84.0
),
),
TestSemantics
(
label:
'pebble'
,
textDirection:
TextDirection
.
ltr
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
rect:
const
Rect
.
fromLTRB
(
52.0
,
48.0
,
228.0
,
84.0
),
),
TestSemantics
(
label:
' in the '
,
textDirection:
TextDirection
.
ltr
,
rect:
const
Rect
.
fromLTRB
(
220.0
,
48.0
,
452.0
,
84.0
),
),
TestSemantics
(
label:
'INTERRUPTION'
,
textDirection:
TextDirection
.
rtl
,
rect:
const
Rect
.
fromLTRB
(
448.0
,
0.0
,
488.0
,
80.0
),
),
TestSemantics
(
label:
'sky'
,
textDirection:
TextDirection
.
ltr
,
rect:
const
Rect
.
fromLTRB
(
484.0
,
48.0
,
576.0
,
84.0
),
),
],
),
],
);
expect
(
semantics
,
hasSemantics
(
expectedSemantics
,
ignoreTransform:
true
,
ignoreId:
true
,));
semantics
.
dispose
();
});
testWidgets
(
'Overflow is clipping correctly - short text with overflow: clip'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Overflow is clipping correctly - short text with overflow: clip'
,
(
WidgetTester
tester
)
async
{
await
_pumpTextWidget
(
await
_pumpTextWidget
(
...
...
packages/flutter/test/widgets/widget_inspector_test.dart
View file @
14414f35
...
@@ -329,7 +329,10 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
...
@@ -329,7 +329,10 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
}
}
// State type is private, hence using dynamic.
// State type is private, hence using dynamic.
dynamic
getInspectorState
()
=>
inspectorKey
.
currentState
;
dynamic
getInspectorState
()
=>
inspectorKey
.
currentState
;
String
paragraphText
(
RenderParagraph
paragraph
)
=>
paragraph
.
text
.
text
;
String
paragraphText
(
RenderParagraph
paragraph
)
{
final
TextSpan
textSpan
=
paragraph
.
text
;
return
textSpan
.
text
;
}
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
Directionality
(
Directionality
(
...
...
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