• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:ui' as ui show ParagraphBuilder, PlaceholderAlignment;
6
7import 'package:flutter/painting.dart';
8
9import 'framework.dart';
10
11/// An immutable widget that is embedded inline within text.
12///
13/// The [child] property is the widget that will be embedded. Children are
14/// constrained by the width of the paragraph.
15///
16/// The [child] property may contain its own [Widget] children (if applicable),
17/// including [Text] and [RichText] widgets which may include additional
18/// [WidgetSpan]s. Child [Text] and [RichText] widgets will be laid out
19/// independently and occupy a rectangular space in the parent text layout.
20///
21/// [WidgetSpan]s will be ignored when passed into a [TextPainter] directly.
22/// To properly layout and paint the [child] widget, [WidgetSpan] should be
23/// passed into a [Text.rich] widget.
24///
25/// {@tool sample}
26///
27/// A card with `Hello World!` embedded inline within a TextSpan tree.
28///
29/// ```dart
30/// Text.rich(
31///   TextSpan(
32///     children: <InlineSpan>[
33///       TextSpan(text: 'Flutter is'),
34///       WidgetSpan(
35///         child: SizedBox(
36///           width: 120,
37///           height: 50,
38///           child: Card(
39///             child: Center(
40///               child: Text('Hello World!')
41///             )
42///           ),
43///         )
44///       ),
45///       TextSpan(text: 'the best!'),
46///     ],
47///   )
48/// )
49/// ```
50/// {@end-tool}
51///
52/// [WidgetSpan] contributes the semantics of the [WidgetSpan.child] to the
53/// semantics tree.
54///
55/// See also:
56///
57///  * [TextSpan], a node that represents text in an [InlineSpan] tree.
58///  * [Text], a widget for showing uniformly-styled text.
59///  * [RichText], a widget for finer control of text rendering.
60///  * [TextPainter], a class for painting [InlineSpan] objects on a [Canvas].
61@immutable
62class WidgetSpan extends PlaceholderSpan {
63  /// Creates a [WidgetSpan] with the given values.
64  ///
65  /// The [child] property must be non-null. [WidgetSpan] is a leaf node in
66  /// the [InlineSpan] tree. Child widgets are constrained by the width of the
67  /// paragraph they occupy. Child widget heights are unconstrained, and may
68  /// cause the text to overflow and be ellipsized/truncated.
69  ///
70  /// A [TextStyle] may be provided with the [style] property, but only the
71  /// decoration, foreground, background, and spacing options will be used.
72  const WidgetSpan({
73    @required this.child,
74    ui.PlaceholderAlignment alignment = ui.PlaceholderAlignment.bottom,
75    TextBaseline baseline,
76    TextStyle style,
77  }) : assert(child != null),
78       assert(baseline != null || !(
79         identical(alignment, ui.PlaceholderAlignment.aboveBaseline) ||
80         identical(alignment, ui.PlaceholderAlignment.belowBaseline) ||
81         identical(alignment, ui.PlaceholderAlignment.baseline)
82       )),
83       super(
84         alignment: alignment,
85         baseline: baseline,
86         style: style,
87       );
88
89  /// The widget to embed inline within text.
90  final Widget child;
91
92  /// Adds a placeholder box to the paragraph builder if a size has been
93  /// calculated for the widget.
94  ///
95  /// Sizes are provided through `dimensions`, which should contain a 1:1
96  /// in-order mapping of widget to laid-out dimensions. If no such dimension
97  /// is provided, the widget will be skipped.
98  ///
99  /// The `textScaleFactor` will be applied to the laid-out size of the widget.
100  @override
101  void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0, @required List<PlaceholderDimensions> dimensions }) {
102    assert(debugAssertIsValid());
103    assert(dimensions != null);
104    final bool hasStyle = style != null;
105    if (hasStyle) {
106      builder.pushStyle(style.getTextStyle(textScaleFactor: textScaleFactor));
107    }
108    assert(builder.placeholderCount < dimensions.length);
109    final PlaceholderDimensions currentDimensions = dimensions[builder.placeholderCount];
110    builder.addPlaceholder(
111      currentDimensions.size.width,
112      currentDimensions.size.height,
113      alignment,
114      scale: textScaleFactor,
115      baseline: currentDimensions.baseline,
116      baselineOffset: currentDimensions.baselineOffset,
117    );
118    if (hasStyle) {
119      builder.pop();
120    }
121  }
122
123  /// Calls `visitor` on this [WidgetSpan]. There are no children spans to walk.
124  @override
125  bool visitChildren(InlineSpanVisitor visitor) {
126    return visitor(this);
127  }
128
129  @override
130  InlineSpan getSpanForPositionVisitor(TextPosition position, Accumulator offset) {
131    return null;
132  }
133
134  @override
135  int codeUnitAtVisitor(int index, Accumulator offset) {
136    return null;
137  }
138
139  @override
140  RenderComparison compareTo(InlineSpan other) {
141    if (identical(this, other))
142      return RenderComparison.identical;
143    if (other.runtimeType != runtimeType)
144      return RenderComparison.layout;
145    if ((style == null) != (other.style == null))
146      return RenderComparison.layout;
147    final WidgetSpan typedOther = other;
148    if (child != typedOther.child || alignment != typedOther.alignment) {
149      return RenderComparison.layout;
150    }
151    RenderComparison result = RenderComparison.identical;
152    if (style != null) {
153      final RenderComparison candidate = style.compareTo(other.style);
154      if (candidate.index > result.index)
155        result = candidate;
156      if (result == RenderComparison.layout)
157        return result;
158    }
159    return result;
160  }
161
162  @override
163  bool operator ==(dynamic other) {
164    if (identical(this, other))
165      return true;
166    if (other.runtimeType != runtimeType)
167      return false;
168    if (super != other)
169      return false;
170    final WidgetSpan typedOther = other;
171    return typedOther.child == child
172        && typedOther.alignment == alignment
173        && typedOther.baseline == baseline;
174  }
175
176  @override
177  int get hashCode => hashValues(super.hashCode, child, alignment, baseline);
178
179  /// Returns the text span that contains the given position in the text.
180  @override
181  InlineSpan getSpanForPosition(TextPosition position) {
182    assert(debugAssertIsValid());
183    return null;
184  }
185
186  /// In debug mode, throws an exception if the object is not in a
187  /// valid configuration. Otherwise, returns true.
188  ///
189  /// This is intended to be used as follows:
190  ///
191  /// ```dart
192  /// assert(myWidgetSpan.debugAssertIsValid());
193  /// ```
194  @override
195  bool debugAssertIsValid() {
196    // WidgetSpans are always valid as asserts prevent invalid WidgetSpans
197    // from being constructed.
198    return true;
199  }
200}
201