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
Expand all
Hide 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,9 +542,11 @@ class TextPainter {
...
@@ -427,9 +542,11 @@ 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
This diff is collapsed.
Click to expand it.
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
This diff is collapsed.
Click to expand it.
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
This diff is collapsed.
Click to expand it.
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