basic.dart 60.6 KB
Newer Older
1 2 3 4
// 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.

5
import 'dart:ui' as ui;
6

7 8
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
9 10

import 'framework.dart';
11

12
export 'package:flutter/rendering.dart' show
13 14 15 16 17 18 19
    BackgroundImage,
    BlockDirection,
    Border,
    BorderSide,
    BoxConstraints,
    BoxDecoration,
    BoxShadow,
20
    BoxShape,
21
    Canvas,
22
    Color,
Adam Barth's avatar
Adam Barth committed
23
    ColorFilter,
24
    CustomClipper,
25
    CustomPainter,
26 27
    Decoration,
    DecorationPosition,
28 29 30 31
    EdgeDims,
    FlexAlignItems,
    FlexDirection,
    FlexJustifyContent,
32 33
    FontStyle,
    FontWeight,
Hixie's avatar
Hixie committed
34
    FractionalOffset,
35
    Gradient,
36
    HitTestBehavior,
37 38
    ImageFit,
    ImageRepeat,
39
    InputEvent,
40
    LinearGradient,
Adam Barth's avatar
Adam Barth committed
41
    Matrix4,
42
    Offset,
Adam Barth's avatar
Adam Barth committed
43
    OneChildLayoutDelegate,
44 45
    Paint,
    Path,
46
    PlainTextSpan,
47
    Point,
48 49 50 51 52
    PointerCancelEvent,
    PointerDownEvent,
    PointerEvent,
    PointerMoveEvent,
    PointerUpEvent,
53
    RadialGradient,
54 55 56
    Rect,
    ScrollDirection,
    Size,
57 58 59 60 61 62 63 64 65
    StyledTextSpan,
    TextAlign,
    TextBaseline,
    TextDecoration,
    TextDecorationStyle,
    TextSpan,
    TextStyle,
    TransferMode,
    ValueChanged,
66
    VoidCallback;
67

68 69 70

// PAINTING NODES

71 72 73 74 75 76 77
/// Makes its child partially transparent.
///
/// This class paints its child into an intermediate buffer and then blends the
/// child back into the scene partially transparent.
///
/// This class is relatively expensive because it requires painting the child
/// into an intermediate buffer.
78
class Opacity extends OneChildRenderObjectWidget {
79
  Opacity({ Key key, this.opacity, Widget child })
80 81 82
    : super(key: key, child: child) {
    assert(opacity >= 0.0 && opacity <= 1.0);
  }
83

84 85 86 87
  /// The fraction to scale the child's alpha value.
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e., invisible).
88 89
  final double opacity;

90
  RenderOpacity createRenderObject() => new RenderOpacity(opacity: opacity);
91

92
  void updateRenderObject(RenderOpacity renderObject, Opacity oldWidget) {
93
    renderObject.opacity = opacity;
94
  }
95 96 97 98 99

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('opacity: $opacity');
  }
100 101
}

Hans Muller's avatar
Hans Muller committed
102 103 104 105
class ShaderMask extends OneChildRenderObjectWidget {
  ShaderMask({
    Key key,
    this.shaderCallback,
106
    this.transferMode: TransferMode.modulate,
Hans Muller's avatar
Hans Muller committed
107 108 109 110 111 112 113
    Widget child
  }) : super(key: key, child: child) {
    assert(shaderCallback != null);
    assert(transferMode != null);
  }

  final ShaderCallback shaderCallback;
114
  final TransferMode transferMode;
Hans Muller's avatar
Hans Muller committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128

  RenderShaderMask createRenderObject() {
    return new RenderShaderMask(
      shaderCallback: shaderCallback,
      transferMode: transferMode
    );
  }

  void updateRenderObject(RenderShaderMask renderObject, ShaderMask oldWidget) {
    renderObject.shaderCallback = shaderCallback;
    renderObject.transferMode = transferMode;
  }
}

129 130 131
/// Paints a [Decoration] either before or after its child paints.
///
/// Commonly used with [BoxDecoration].
132
class DecoratedBox extends OneChildRenderObjectWidget {
133 134 135
  DecoratedBox({
    Key key,
    this.decoration,
136
    this.position: DecorationPosition.background,
137
    Widget child
138 139 140 141
  }) : super(key: key, child: child) {
    assert(decoration != null);
    assert(position != null);
  }
142

143
  /// What decoration to paint.
144 145
  ///
  /// Commonly a [BoxDecoration].
146
  final Decoration decoration;
147 148

  /// Where to paint the box decoration.
149
  final DecorationPosition position;
150

151
  RenderDecoratedBox createRenderObject() => new RenderDecoratedBox(decoration: decoration, position: position);
152

153
  void updateRenderObject(RenderDecoratedBox renderObject, DecoratedBox oldWidget) {
154
    renderObject.decoration = decoration;
155
    renderObject.position = position;
156 157 158
  }
}

159 160 161 162 163 164 165 166 167 168 169 170 171
/// Delegates its painting.
///
/// When asked to paint, custom paint first asks painter to paint with the
/// current canvas and then paints its children. After painting its children,
/// custom paint asks foregroundPainter to paint. The coodinate system of the
/// canvas matches the coordinate system of the custom paint object. The
/// painters are expected to paint within a rectangle starting at the origin
/// and encompassing a region of the given size. If the painters paints outside
/// those bounds, there might be insufficient memory allocated to rasterize the
/// painting commands and the resulting behavior is undefined.
///
/// Because custom paint calls its painters during paint, you cannot dirty
/// layout or paint information during the callback.
172
class CustomPaint extends OneChildRenderObjectWidget {
173 174
  CustomPaint({ Key key, this.painter, this.foregroundPainter, Widget child })
    : super(key: key, child: child);
175

176
  /// The painter that paints before the children.
177
  final CustomPainter painter;
178 179

  /// The painter that paints after the children.
180
  final CustomPainter foregroundPainter;
Adam Barth's avatar
Adam Barth committed
181

182 183 184 185
  RenderCustomPaint createRenderObject() => new RenderCustomPaint(
    painter: painter,
    foregroundPainter: foregroundPainter
  );
186

187
  void updateRenderObject(RenderCustomPaint renderObject, CustomPaint oldWidget) {
188
    renderObject.painter = painter;
189
    renderObject.foregroundPainter = foregroundPainter;
190 191
  }

192
  void didUnmountRenderObject(RenderCustomPaint renderObject) {
193
    renderObject.painter = null;
194
    renderObject.foregroundPainter = null;
195 196 197
  }
}

198 199 200
/// Clips its child using a rectangle.
///
/// Prevents its child from painting outside its bounds.
201
class ClipRect extends OneChildRenderObjectWidget {
202 203
  ClipRect({ Key key, this.clipper, Widget child }) : super(key: key, child: child);

204
  /// If non-null, determines which clip to use.
205 206 207 208 209 210 211 212 213 214 215
  final CustomClipper<Rect> clipper;

  RenderClipRect createRenderObject() => new RenderClipRect(clipper: clipper);

  void updateRenderObject(RenderClipRect renderObject, ClipRect oldWidget) {
    renderObject.clipper = clipper;
  }

  void didUnmountRenderObject(RenderClipRect renderObject) {
    renderObject.clipper = null;
  }
216 217
}

218 219 220 221 222
/// Clips its child using a rounded rectangle.
///
/// Creates a rounded rectangle from its layout dimensions and the given x and
/// y radius values and prevents its child from painting outside that rounded
/// rectangle.
223
class ClipRRect extends OneChildRenderObjectWidget {
224
  ClipRRect({ Key key, this.xRadius, this.yRadius, Widget child })
225 226
    : super(key: key, child: child);

227 228 229 230
  /// The radius of the rounded corners in the horizontal direction in logical pixels.
  ///
  /// Values are clamped to be between zero and half the width of the render
  /// object.
231
  final double xRadius;
232 233 234 235 236

  /// The radius of the rounded corners in the vertical direction in logical pixels.
  ///
  /// Values are clamped to be between zero and half the height of the render
  /// object.
237 238
  final double yRadius;

239
  RenderClipRRect createRenderObject() => new RenderClipRRect(xRadius: xRadius, yRadius: yRadius);
240

241
  void updateRenderObject(RenderClipRRect renderObject, ClipRRect oldWidget) {
242 243
    renderObject.xRadius = xRadius;
    renderObject.yRadius = yRadius;
244 245 246
  }
}

247 248 249 250
/// Clips its child using an oval.
///
/// Inscribes an oval into its layout dimensions and prevents its child from
/// painting outside that oval.
251
class ClipOval extends OneChildRenderObjectWidget {
252 253
  ClipOval({ Key key, this.clipper, Widget child }) : super(key: key, child: child);

254
  /// If non-null, determines which clip to use.
255 256 257 258 259 260 261 262 263 264 265
  final CustomClipper<Rect> clipper;

  RenderClipOval createRenderObject() => new RenderClipOval(clipper: clipper);

  void updateRenderObject(RenderClipOval renderObject, ClipOval oldWidget) {
    renderObject.clipper = clipper;
  }

  void didUnmountRenderObject(RenderClipOval renderObject) {
    renderObject.clipper = null;
  }
266 267 268 269 270
}


// POSITIONING AND SIZING NODES

271
/// Applies a transformation before painting its child.
272
class Transform extends OneChildRenderObjectWidget {
Hixie's avatar
Hixie committed
273
  Transform({ Key key, this.transform, this.origin, this.alignment, Widget child })
274 275 276
    : super(key: key, child: child) {
    assert(transform != null);
  }
277

278
  /// The matrix to transform the child by during painting.
279
  final Matrix4 transform;
280 281 282 283 284 285

  /// The origin of the coordinate system (relative to the upper left corder of
  /// this render object) in which to apply the matrix.
  ///
  /// Setting an origin is equivalent to conjugating the transform matrix by a
  /// translation. This property is provided just for convenience.
286
  final Offset origin;
287 288 289 290 291

  /// The alignment of the origin, relative to the size of the box.
  ///
  /// This is equivalent to setting an origin based on the size of the box.
  /// If it is specificed at the same time as an offset, both are applied.
Hixie's avatar
Hixie committed
292
  final FractionalOffset alignment;
293

Hixie's avatar
Hixie committed
294
  RenderTransform createRenderObject() => new RenderTransform(transform: transform, origin: origin, alignment: alignment);
295

296
  void updateRenderObject(RenderTransform renderObject, Transform oldWidget) {
297
    renderObject.transform = transform;
298
    renderObject.origin = origin;
Hixie's avatar
Hixie committed
299
    renderObject.alignment = alignment;
300 301 302
  }
}

303 304 305 306 307 308
/// Insets its child by the given padding.
///
/// When passing layout constraints to its child, padding shrinks the
/// constraints by the given padding, causing the child to layout at a smaller
/// size. Padding then sizes itself to its child's size, inflated by the
/// padding, effectively creating empty space around the child.
309
class Padding extends OneChildRenderObjectWidget {
310
  Padding({ Key key, this.padding, Widget child })
311 312 313
    : super(key: key, child: child) {
    assert(padding != null);
  }
314

315
  /// The amount to pad the child in each dimension.
316 317
  final EdgeDims padding;

318
  RenderPadding createRenderObject() => new RenderPadding(padding: padding);
319

320
  void updateRenderObject(RenderPadding renderObject, Padding oldWidget) {
321
    renderObject.padding = padding;
322 323 324
  }
}

325 326 327 328 329
/// Aligns its child box within itself.
///
/// For example, to align a box at the bottom right, you would pass this box a
/// tight constraint that is bigger than the child's natural size,
/// with horizontal and vertical set to 1.0.
330
class Align extends OneChildRenderObjectWidget {
331 332
  Align({
    Key key,
333
    this.alignment: const FractionalOffset(0.5, 0.5),
334 335
    this.widthFactor,
    this.heightFactor,
336
    Widget child
337 338 339 340 341
  }) : super(key: key, child: child) {
    assert(alignment != null && alignment.x != null && alignment.y != null);
    assert(widthFactor == null || widthFactor >= 0.0);
    assert(heightFactor == null || heightFactor >= 0.0);
  }
342

343 344 345 346 347 348 349 350 351
  /// How to align the child.
  ///
  /// The x and y values of the alignment control the horizontal and vertical
  /// alignment, respectively.  An x value of 0.0 means that the left edge of
  /// the child is aligned with the left edge of the parent whereas an x value
  /// of 1.0 means that the right edge of the child is aligned with the right
  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
  /// For example, a value of 0.5 means that the center of the child is aligned
  /// with the center of the parent.
352
  final FractionalOffset alignment;
353 354 355 356

  /// If non-null, sets its width to the child's width multipled by this factor.
  ///
  /// Can be both greater and less than 1.0 but must be positive.
357
  final double widthFactor;
358 359 360 361

  /// If non-null, sets its height to the child's height multipled by this factor.
  ///
  /// Can be both greater and less than 1.0 but must be positive.
362
  final double heightFactor;
363

364
  RenderPositionedBox createRenderObject() => new RenderPositionedBox(alignment: alignment, widthFactor: widthFactor, heightFactor: heightFactor);
365

366
  void updateRenderObject(RenderPositionedBox renderObject, Align oldWidget) {
367
    renderObject.alignment = alignment;
368 369
    renderObject.widthFactor = widthFactor;
    renderObject.heightFactor = heightFactor;
370 371 372
  }
}

373
/// Centers its child within itself.
374
class Center extends Align {
375 376
  Center({ Key key, widthFactor, heightFactor, Widget child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
377 378
}

379 380 381 382 383 384
/// Defers the layout of its single child to a delegate.
///
/// The delegate can determine the layout constraints for the child and can
/// decide where to position the child. The delegate can also determine the size
/// of the parent, but the size of the parent cannot depend on the size of the
/// child.
Adam Barth's avatar
Adam Barth committed
385 386 387 388 389 390 391 392 393 394 395 396 397 398
class CustomOneChildLayout extends OneChildRenderObjectWidget {
  CustomOneChildLayout({
    Key key,
    this.delegate,
    Widget child
  }) : super(key: key, child: child) {
    assert(delegate != null);
  }

  final OneChildLayoutDelegate delegate;

  RenderCustomOneChildLayoutBox createRenderObject() => new RenderCustomOneChildLayoutBox(delegate: delegate);

  void updateRenderObject(RenderCustomOneChildLayoutBox renderObject, CustomOneChildLayout oldWidget) {
399 400 401 402
    renderObject.delegate = delegate;
  }
}

403
/// Metadata for identifying children in a [CustomMultiChildLayout].
404 405 406 407
class LayoutId extends ParentDataWidget {
  LayoutId({
    Key key,
    Widget child,
408 409
    Object id
  }) : id = id, super(key: key ?? new ValueKey(id), child: child) {
410
    assert(child != null);
411
    assert(id != null);
412
  }
413

414
  /// An object representing the identity of this child.
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
  final Object id;

  void debugValidateAncestor(Widget ancestor) {
    assert(() {
      'LayoutId must placed inside a CustomMultiChildLayout';
      return ancestor is CustomMultiChildLayout;
    });
  }

  void applyParentData(RenderObject renderObject) {
    assert(renderObject.parentData is MultiChildLayoutParentData);
    final MultiChildLayoutParentData parentData = renderObject.parentData;
    if (parentData.id != id) {
      parentData.id = id;
      AbstractNode targetParent = renderObject.parent;
      if (targetParent is RenderObject)
        targetParent.markNeedsLayout();
    }
  }

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('id: $id');
  }
}

441 442 443 444 445 446
/// Defers the layout of multiple children to a delegate.
///
/// The delegate can determine the layout constraints for each child and can
/// decide where to position each child. The delegate can also determine the
/// size of the parent, but the size of the parent cannot depend on the sizes of
/// the children.
447 448 449
class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
  CustomMultiChildLayout(List<Widget> children, {
    Key key,
450
    this.delegate
451 452 453 454
  }) : super(key: key, children: children) {
    assert(delegate != null);
  }

455
  /// The delegate that controls the layout of the children.
456 457 458 459 460 461 462
  final MultiChildLayoutDelegate delegate;

  RenderCustomMultiChildLayoutBox createRenderObject() {
    return new RenderCustomMultiChildLayoutBox(delegate: delegate);
  }

  void updateRenderObject(RenderCustomMultiChildLayoutBox renderObject, CustomMultiChildLayout oldWidget) {
Adam Barth's avatar
Adam Barth committed
463 464 465 466
    renderObject.delegate = delegate;
  }
}

467 468 469 470
/// A box with a specified size.
///
/// Forces its child to have a specific width and/or height and sizes itself to
/// match the size of its child.
471
class SizedBox extends OneChildRenderObjectWidget {
472
  SizedBox({ Key key, this.width, this.height, Widget child })
473 474
    : super(key: key, child: child);

475
  /// If non-null, requires the child to have exactly this width.
476
  final double width;
477 478

  /// If non-null, requires the child to have exactly this height.
479 480
  final double height;

481 482 483
  RenderConstrainedBox createRenderObject() => new RenderConstrainedBox(
    additionalConstraints: _additionalConstraints
  );
484

485
  BoxConstraints get _additionalConstraints {
486 487
    BoxConstraints result = const BoxConstraints();
    if (width != null)
488
      result = result.tightenWidth(width);
489
    if (height != null)
490
      result = result.tightenHeight(height);
491 492 493
    return result;
  }

494 495
  void updateRenderObject(RenderConstrainedBox renderObject, SizedBox oldWidget) {
    renderObject.additionalConstraints = _additionalConstraints;
496
  }
497 498 499 500 501 502 503 504

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (width != null)
      description.add('width: $width');
    if (height != null)
      description.add('height: $height');
  }
505 506
}

507 508 509 510 511
/// Imposes additional constraints on its child.
///
/// For example, if you wanted [child] to have a minimum height of 50.0 logical
/// pixels, you could use `const BoxConstraints(minHeight: 50.0)`` as the
/// [additionalConstraints].
512
class ConstrainedBox extends OneChildRenderObjectWidget {
513
  ConstrainedBox({ Key key, this.constraints, Widget child })
514 515 516
    : super(key: key, child: child) {
    assert(constraints != null);
  }
517

518
  /// The additional constraints to impose on the child.
519 520
  final BoxConstraints constraints;

521
  RenderConstrainedBox createRenderObject() => new RenderConstrainedBox(additionalConstraints: constraints);
522

523
  void updateRenderObject(RenderConstrainedBox renderObject, ConstrainedBox oldWidget) {
524
    renderObject.additionalConstraints = constraints;
525
  }
526 527 528

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
Hixie's avatar
Hixie committed
529
    description.add('$constraints');
530
  }
531 532
}

533 534 535
/// Sizes itself to a fraction of the total available space.
///
/// See [RenderFractionallySizedBox] for details.
Hixie's avatar
Hixie committed
536 537 538 539
class FractionallySizedBox extends OneChildRenderObjectWidget {
  FractionallySizedBox({ Key key, this.width, this.height, Widget child })
    : super(key: key, child: child);

540 541 542 543
  /// If non-null, the factor of the incoming width to use.
  ///
  /// If non-null, the child is given a tight width constraint that is the max
  /// incoming width constraint multipled by this factor.
Hixie's avatar
Hixie committed
544
  final double width;
545 546 547 548 549

  /// If non-null, the factor of the incoming height to use.
  ///
  /// If non-null, the child is given a tight height constraint that is the max
  /// incoming height constraint multipled by this factor.
Hixie's avatar
Hixie committed
550 551 552 553 554 555 556
  final double height;

  RenderFractionallySizedBox createRenderObject() => new RenderFractionallySizedBox(
    widthFactor: width,
    heightFactor: height
  );

557
  void updateRenderObject(RenderFractionallySizedBox renderObject, FractionallySizedBox oldWidget) {
Hixie's avatar
Hixie committed
558 559 560
    renderObject.widthFactor = width;
    renderObject.heightFactor = height;
  }
561 562 563 564 565 566 567 568

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (width != null)
      description.add('width: $width');
    if (height != null)
      description.add('height: $height');
  }
Hixie's avatar
Hixie committed
569 570
}

571 572 573 574
/// A render object that imposes different constraints on its child than it gets
/// from its parent, possibly allowing the child to overflow the parent.
///
/// See [RenderOverflowBox] for details.
575 576 577 578
class OverflowBox extends OneChildRenderObjectWidget {
  OverflowBox({ Key key, this.minWidth, this.maxWidth, this.minHeight, this.maxHeight, Widget child })
    : super(key: key, child: child);

579 580
  /// The minimum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
581
  final double minWidth;
582 583 584

  /// The maximum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
585
  final double maxWidth;
586 587 588

  /// The minimum height constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
589
  final double minHeight;
590 591 592

  /// The maximum height constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
  final double maxHeight;

  RenderOverflowBox createRenderObject() => new RenderOverflowBox(
    minWidth: minWidth,
    maxWidth: maxWidth,
    minHeight: minHeight,
    maxHeight: maxHeight
  );

  void updateRenderObject(RenderOverflowBox renderObject, OverflowBox oldWidget) {
    renderObject.minWidth = minWidth;
    renderObject.maxWidth = maxWidth;
    renderObject.minHeight = minHeight;
    renderObject.maxHeight = maxHeight;
  }
}

610 611 612 613 614 615 616 617 618 619 620 621 622
class SizedOverflowBox extends OneChildRenderObjectWidget {
  SizedOverflowBox({ Key key, this.size, Widget child })
    : super(key: key, child: child);

  final Size size;

  RenderSizedOverflowBox createRenderObject() => new RenderSizedOverflowBox(requestedSize: size);

  void updateRenderObject(RenderSizedOverflowBox renderObject, SizedOverflowBox oldWidget) {
    renderObject.requestedSize = size;
  }
}

623 624 625
/// Lays the child out as if it was in the tree, but without painting anything,
/// without making the child available for hit testing, and without taking any
/// room in the parent.
Hixie's avatar
Hixie committed
626 627 628 629 630 631 632
class OffStage extends OneChildRenderObjectWidget {
  OffStage({ Key key, Widget child })
    : super(key: key, child: child);

  RenderOffStage createRenderObject() => new RenderOffStage();
}

633 634 635
/// Forces child to layout at a specific aspect ratio.
///
/// See [RenderAspectRatio] for details.
636
class AspectRatio extends OneChildRenderObjectWidget {
637
  AspectRatio({ Key key, this.aspectRatio, Widget child })
638 639 640
    : super(key: key, child: child) {
    assert(aspectRatio != null);
  }
641

642 643 644 645
  /// The aspect ratio to use when computing the height from the width.
  ///
  /// The aspect ratio is expressed as a ratio of width to height. For example,
  /// a 16:9 width:height aspect ratio would have a value of 16.0/9.0.
646 647
  final double aspectRatio;

648
  RenderAspectRatio createRenderObject() => new RenderAspectRatio(aspectRatio: aspectRatio);
649

650
  void updateRenderObject(RenderAspectRatio renderObject, AspectRatio oldWidget) {
651
    renderObject.aspectRatio = aspectRatio;
652
  }
653 654 655 656 657

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('aspectRatio: $aspectRatio');
  }
658 659
}

660 661 662 663 664 665 666 667 668 669 670 671
/// Sizes its child to the child's intrinsic width.
///
/// Sizes its child's width to the child's maximum intrinsic width. If
/// [stepWidth] is non-null, the child's width will be snapped to a multiple of
/// the [stepWidth]. Similarly, if [stepHeight] is non-null, the child's height
/// will be snapped to a multiple of the [stepHeight].
///
/// This class is useful, for example, when unlimited width is available and
/// you would like a child that would otherwise attempt to expand infinitely to
/// instead size itself to a more reasonable width.
///
/// This class is relatively expensive. Avoid using it where possible.
672
class IntrinsicWidth extends OneChildRenderObjectWidget {
673
  IntrinsicWidth({ Key key, this.stepWidth, this.stepHeight, Widget child })
674 675
    : super(key: key, child: child);

676
  /// If non-null, force the child's width to be a multiple of this value.
677
  final double stepWidth;
678 679

  /// If non-null, force the child's height to be a multiple of this value.
680 681
  final double stepHeight;

682
  RenderIntrinsicWidth createRenderObject() => new RenderIntrinsicWidth(stepWidth: stepWidth, stepHeight: stepHeight);
683

684
  void updateRenderObject(RenderIntrinsicWidth renderObject, IntrinsicWidth oldWidget) {
685 686
    renderObject.stepWidth = stepWidth;
    renderObject.stepHeight = stepHeight;
687 688 689
  }
}

690 691 692 693 694 695 696
/// Sizes its child to the child's intrinsic height.
///
/// This class is useful, for example, when unlimited height is available and
/// you would like a child that would otherwise attempt to expand infinitely to
/// instead size itself to a more reasonable height.
///
/// This class is relatively expensive. Avoid using it where possible.
697 698 699
class IntrinsicHeight extends OneChildRenderObjectWidget {
  IntrinsicHeight({ Key key, Widget child }) : super(key: key, child: child);
  RenderIntrinsicHeight createRenderObject() => new RenderIntrinsicHeight();
Hixie's avatar
Hixie committed
700 701
}

702
/// Positions its child vertically according to the child's baseline.
703
class Baseline extends OneChildRenderObjectWidget {
704
  Baseline({ Key key, this.baseline, this.baselineType: TextBaseline.alphabetic, Widget child })
705 706 707 708
    : super(key: key, child: child) {
    assert(baseline != null);
    assert(baselineType != null);
  }
709

710 711 712 713 714
  /// The number of logical pixels from the top of this box at which to position
  /// the child's baseline.
  final double baseline;

  /// The type of baseline to use for positioning the child.
715 716
  final TextBaseline baselineType;

717
  RenderBaseline createRenderObject() => new RenderBaseline(baseline: baseline, baselineType: baselineType);
718

719
  void updateRenderObject(RenderBaseline renderObject, Baseline oldWidget) {
720 721
    renderObject.baseline = baseline;
    renderObject.baselineType = baselineType;
722 723 724
  }
}

725 726 727 728 729 730 731 732 733
/// A widget that's bigger on the inside.
///
/// The child of a viewport can layout to a larger size than the viewport
/// itself. If that happens, only a portion of the child will be visible through
/// the viewport. The portion of the child that is visible is controlled by the
/// scroll offset.
///
/// Viewport is the core scrolling primitive in the system, but it can be used
/// in other situations.
734
class Viewport extends OneChildRenderObjectWidget {
735 736
  Viewport({
    Key key,
737
    this.scrollDirection: ScrollDirection.vertical,
738
    this.scrollOffset: Offset.zero,
739
    Widget child
740 741 742 743
  }) : super(key: key, child: child) {
    assert(scrollDirection != null);
    assert(scrollOffset != null);
  }
744

745 746 747 748 749
  /// The direction in which the child is permitted to be larger than the viewport
  ///
  /// If the viewport is scrollable in a particular direction (e.g., vertically),
  /// the child is given layout constraints that are fully unconstrainted in
  /// that direction (e.g., the child can be as tall as it wants).
750
  final ScrollDirection scrollDirection;
751 752 753 754

  /// The offset at which to paint the child.
  ///
  /// The offset can be non-zero only in the [scrollDirection].
755
  final Offset scrollOffset;
756

757
  RenderViewport createRenderObject() => new RenderViewport(scrollDirection: scrollDirection, scrollOffset: scrollOffset);
758

759
  void updateRenderObject(RenderViewport renderObject, Viewport oldWidget) {
760
    // Order dependency: RenderViewport validates scrollOffset based on scrollDirection.
761
    renderObject.scrollDirection = scrollDirection;
762
    renderObject.scrollOffset = scrollOffset;
763 764 765
  }
}

766 767 768 769
/// Calls [onSizeChanged] whenever the child's layout size changes
///
/// Because size observer calls its callback during layout, you cannot modify
/// layout information during the callback.
770
class SizeObserver extends OneChildRenderObjectWidget {
771
  SizeObserver({ Key key, this.onSizeChanged, Widget child })
772
    : super(key: key, child: child) {
773
    assert(onSizeChanged != null);
774
  }
775

776
  /// The callback to call whenever the child's layout size changes
777
  final SizeChangedCallback onSizeChanged;
778

779
  RenderSizeObserver createRenderObject() => new RenderSizeObserver(onSizeChanged: onSizeChanged);
780

781
  void updateRenderObject(RenderSizeObserver renderObject, SizeObserver oldWidget) {
782
    renderObject.onSizeChanged = onSizeChanged;
783 784
  }

785
  void didUnmountRenderObject(RenderSizeObserver renderObject) {
786
    renderObject.onSizeChanged = null;
787 788 789 790
  }
}


791
// CONTAINER
792

793
/// A convenience widget that combines common painting, positioning, and sizing widgets.
794
class Container extends StatelessComponent {
795
  Container({
796
    Key key,
797 798 799
    this.child,
    this.constraints,
    this.decoration,
800
    this.foregroundDecoration,
801 802
    this.margin,
    this.padding,
Adam Barth's avatar
Adam Barth committed
803 804 805
    this.transform,
    this.width,
    this.height
806 807 808
  }) : super(key: key) {
    assert(margin == null || margin.isNonNegative);
    assert(padding == null || padding.isNonNegative);
809
    assert(decoration == null || decoration.debugAssertValid());
810
  }
811

812 813 814
  /// The child to contain in the container.
  ///
  /// If null, the container will expand to fill all available space in its parent.
815
  final Widget child;
816 817

  /// Additional constraints to apply to the child.
818
  final BoxConstraints constraints;
819 820

  /// The decoration to paint behind the child.
821
  final Decoration decoration;
822 823

  /// The decoration to paint in front of the child.
824
  final Decoration foregroundDecoration;
825 826

  /// Empty space to surround the decoration.
827
  final EdgeDims margin;
828 829

  /// Empty space to inscribe inside the decoration.
830
  final EdgeDims padding;
831

832
  /// The transformation matrix to apply before painting the container.
833
  final Matrix4 transform;
834 835

  /// If non-null, requires the decoration to have this width.
836
  final double width;
837 838

  /// If non-null, requires the decoration to have this height.
839 840
  final double height;

841 842
  EdgeDims get _paddingIncludingDecoration {
    if (decoration == null || decoration.padding == null)
843
      return padding;
844
    EdgeDims decorationPadding = decoration.padding;
845
    if (padding == null)
846 847
      return decorationPadding;
    return padding + decorationPadding;
848 849
  }

850
  Widget build(BuildContext context) {
851 852
    Widget current = child;

853
    if (child == null && (width == null || height == null))
854
      current = new ConstrainedBox(constraints: const BoxConstraints.expand());
855

856
    EdgeDims effectivePadding = _paddingIncludingDecoration;
857 858
    if (effectivePadding != null)
      current = new Padding(padding: effectivePadding, child: current);
859 860 861 862

    if (decoration != null)
      current = new DecoratedBox(decoration: decoration, child: current);

863 864 865
    if (foregroundDecoration != null) {
      current = new DecoratedBox(
        decoration: foregroundDecoration,
866
        position: DecorationPosition.foreground,
867 868 869 870
        child: current
      );
    }

871
    if (width != null || height != null) {
872 873 874 875 876
      current = new SizedBox(
        width: width,
        height: height,
        child: current
      );
877
    }
878 879 880 881 882 883 884 885 886 887 888 889 890

    if (constraints != null)
      current = new ConstrainedBox(constraints: constraints, child: current);

    if (margin != null)
      current = new Padding(padding: margin, child: current);

    if (transform != null)
      current = new Transform(transform: transform, child: current);

    return current;
  }

Hixie's avatar
Hixie committed
891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (constraints != null)
      description.add('$constraints');
    if (decoration != null)
      description.add('has background');
    if (foregroundDecoration != null)
      description.add('has foreground');
    if (margin != null)
      description.add('margin: $margin');
    if (padding != null)
      description.add('padding: $padding');
    if (transform != null)
      description.add('has transform');
    if (width != null)
      description.add('width: $width');
    if (height != null)
      description.add('height: $height');
  }
910 911 912 913 914
}


// LAYOUT NODES

915 916 917 918 919 920
/// Uses the block layout algorithm for its children.
///
/// This widget is rarely used directly. Instead, consider using [Block], which
/// combines the block layout algorithm with scrolling behavior.
///
/// For details about the block layout algorithm, see [RenderBlockBase].
921
class BlockBody extends MultiChildRenderObjectWidget {
922
  BlockBody(List<Widget> children, {
923 924
    Key key,
    this.direction: BlockDirection.vertical
925 926 927
  }) : super(key: key, children: children) {
    assert(direction != null);
  }
928

929
  /// The direction to use as the main axis.
930 931
  final BlockDirection direction;

932
  RenderBlock createRenderObject() => new RenderBlock(direction: direction);
933

934
  void updateRenderObject(RenderBlock renderObject, BlockBody oldWidget) {
935
    renderObject.direction = direction;
936
  }
937 938
}

Adam Barth's avatar
Adam Barth committed
939 940 941 942
/// Uses the stack layout algorithm for its children.
///
/// For details about the stack layout algorithm, see [RenderStack]. To control
/// the position of child widgets, see the [Positioned] widget.
943
class Stack extends MultiChildRenderObjectWidget {
Hans Muller's avatar
Hans Muller committed
944 945
  Stack(List<Widget> children, {
    Key key,
946 947
    this.alignment: const FractionalOffset(0.0, 0.0)
  }) : super(key: key, children: children);
Hans Muller's avatar
Hans Muller committed
948

Adam Barth's avatar
Adam Barth committed
949
  /// How to align the non-positioned children in the stack.
950
  final FractionalOffset alignment;
Hans Muller's avatar
Hans Muller committed
951

952
  RenderStack createRenderObject() => new RenderStack(alignment: alignment);
Hans Muller's avatar
Hans Muller committed
953 954

  void updateRenderObject(RenderStack renderObject, Stack oldWidget) {
955
    renderObject.alignment = alignment;
Hans Muller's avatar
Hans Muller committed
956 957 958
  }
}

Adam Barth's avatar
Adam Barth committed
959
/// A [Stack] that shows a single child at once.
Hans Muller's avatar
Hans Muller committed
960 961 962
class IndexedStack extends MultiChildRenderObjectWidget {
  IndexedStack(List<Widget> children, {
    Key key,
963
    this.alignment: const FractionalOffset(0.0, 0.0),
Hans Muller's avatar
Hans Muller committed
964
    this.index: 0
965 966 967
  }) : super(key: key, children: children) {
    assert(index != null);
  }
Hans Muller's avatar
Hans Muller committed
968

Adam Barth's avatar
Adam Barth committed
969
  /// The index of the child to show.
Hans Muller's avatar
Hans Muller committed
970
  final int index;
Adam Barth's avatar
Adam Barth committed
971 972

  /// How to align the non-positioned children in the stack.
973 974 975
  final FractionalOffset alignment;

  RenderIndexedStack createRenderObject() => new RenderIndexedStack(index: index, alignment: alignment);
Hans Muller's avatar
Hans Muller committed
976 977 978 979

  void updateRenderObject(RenderIndexedStack renderObject, IndexedStack oldWidget) {
    super.updateRenderObject(renderObject, oldWidget);
    renderObject.index = index;
980
    renderObject.alignment = alignment;
Hans Muller's avatar
Hans Muller committed
981
  }
982 983
}

Adam Barth's avatar
Adam Barth committed
984 985 986 987 988
/// Controls where a child of a [Stack] is positioned.
///
/// This widget must be a descendant of a [Stack], and the path from this widget
/// to its enclosing [Stack] must contain only components (e.g., not other
/// kinds of widgets, like [RenderObjectWidget]s).
989 990 991 992 993 994 995
class Positioned extends ParentDataWidget {
  Positioned({
    Key key,
    Widget child,
    this.top,
    this.right,
    this.bottom,
996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
    this.left,
    this.width,
    this.height
  }) : super(key: key, child: child) {
    assert(top == null || bottom == null || height == null);
    assert(left == null || right == null || width == null);
  }

  Positioned.fromRect({
    Key key,
    Widget child,
    Rect rect
  }) : left = rect.left,
       top = rect.top,
       width = rect.width,
       height = rect.height,
1012 1013
       right = null,
       bottom = null,
1014
       super(key: key, child: child);
1015

Adam Barth's avatar
Adam Barth committed
1016
  /// The offset of the child's top edge from the top of the stack.
1017
  final double top;
Adam Barth's avatar
Adam Barth committed
1018 1019

  /// The offset of the child's right edge from the right of the stack.
1020
  final double right;
Adam Barth's avatar
Adam Barth committed
1021 1022

  /// The offset of the child's bottom edge from the bottom of the stack.
1023
  final double bottom;
Adam Barth's avatar
Adam Barth committed
1024 1025

  /// The offset of the child's left edge from the left of the stack.
1026
  final double left;
1027

Adam Barth's avatar
Adam Barth committed
1028 1029 1030
  /// The child's width.
  ///
  /// Ignored if both left and right are non-null.
1031
  final double width;
Adam Barth's avatar
Adam Barth committed
1032 1033 1034 1035

  /// The child's height.
  ///
  /// Ignored if both top and bottom are non-null.
1036 1037
  final double height;

1038 1039 1040 1041 1042
  void debugValidateAncestor(Widget ancestor) {
    assert(() {
      'Positioned must placed inside a Stack';
      return ancestor is Stack;
    });
1043 1044
  }

1045 1046 1047
  void applyParentData(RenderObject renderObject) {
    assert(renderObject.parentData is StackParentData);
    final StackParentData parentData = renderObject.parentData;
1048
    bool needsLayout = false;
1049

1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069
    if (parentData.top != top) {
      parentData.top = top;
      needsLayout = true;
    }

    if (parentData.right != right) {
      parentData.right = right;
      needsLayout = true;
    }

    if (parentData.bottom != bottom) {
      parentData.bottom = bottom;
      needsLayout = true;
    }

    if (parentData.left != left) {
      parentData.left = left;
      needsLayout = true;
    }

1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
    if (parentData.width != width) {
      parentData.width = width;
      needsLayout = true;
    }

    if (parentData.height != height) {
      parentData.height = height;
      needsLayout = true;
    }

1080 1081 1082 1083 1084
    if (needsLayout) {
      AbstractNode targetParent = renderObject.parent;
      if (targetParent is RenderObject)
        targetParent.markNeedsLayout();
    }
1085
  }
1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (left != null)
      description.add('left: $left');
    if (top != null)
      description.add('top: $top');
    if (right != null)
      description.add('right: $right');
    if (bottom != null)
      description.add('bottom: $bottom');
1097 1098 1099 1100
    if (width != null)
      description.add('width: $width');
    if (height != null)
      description.add('height: $height');
1101
  }
1102 1103
}

Adam Barth's avatar
Adam Barth committed
1104 1105 1106
/// Uses the grid layout algorithm for its children.
///
/// For details about the grid layout algorithm, see [RenderGrid].
1107
class Grid extends MultiChildRenderObjectWidget {
1108
  Grid(List<Widget> children, { Key key, this.maxChildExtent })
1109 1110 1111 1112 1113 1114
    : super(key: key, children: children) {
    assert(maxChildExtent != null);
  }

  final double maxChildExtent;

1115
  RenderGrid createRenderObject() => new RenderGrid(maxChildExtent: maxChildExtent);
1116

1117
  void updateRenderObject(RenderGrid renderObject, Grid oldWidget) {
1118 1119 1120
    renderObject.maxChildExtent = maxChildExtent;
  }
}
1121

Adam Barth's avatar
Adam Barth committed
1122 1123 1124 1125
/// Uses the flex layout algorithm for its children.
///
/// For details about the flex layout algorithm, see [RenderFlex]. To control
/// the flex of child widgets, see the [Flexible] widget.
1126
class Flex extends MultiChildRenderObjectWidget {
1127
  Flex(List<Widget> children, {
1128
    Key key,
1129 1130
    this.direction: FlexDirection.horizontal,
    this.justifyContent: FlexJustifyContent.start,
1131 1132
    this.alignItems: FlexAlignItems.center,
    this.textBaseline
1133 1134 1135 1136 1137
  }) : super(key: key, children: children) {
    assert(direction != null);
    assert(justifyContent != null);
    assert(alignItems != null);
  }
1138 1139 1140 1141

  final FlexDirection direction;
  final FlexJustifyContent justifyContent;
  final FlexAlignItems alignItems;
1142
  final TextBaseline textBaseline;
1143

1144
  RenderFlex createRenderObject() => new RenderFlex(direction: direction, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline);
1145

1146
  void updateRenderObject(RenderFlex renderObject, Flex oldWidget) {
1147 1148 1149 1150
    renderObject.direction = direction;
    renderObject.justifyContent = justifyContent;
    renderObject.alignItems = alignItems;
    renderObject.textBaseline = textBaseline;
1151 1152 1153
  }
}

Adam Barth's avatar
Adam Barth committed
1154 1155 1156 1157
/// Lays out child elements in a row.
///
/// For details about the flex layout algorithm, see [RenderFlex]. To control
/// the flex of child widgets, see the [Flexible] widget.
1158
class Row extends Flex {
1159
  Row(List<Widget> children, {
1160 1161 1162 1163
    Key key,
    justifyContent: FlexJustifyContent.start,
    alignItems: FlexAlignItems.center,
    textBaseline
1164
  }) : super(children, key: key, direction: FlexDirection.horizontal, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline);
1165 1166
}

Adam Barth's avatar
Adam Barth committed
1167 1168 1169 1170
/// Lays out child elements in a column.
///
/// For details about the flex layout algorithm, see [RenderFlex]. To control
/// the flex of child widgets, see the [Flexible] widget.
1171
class Column extends Flex {
1172
  Column(List<Widget> children, {
1173 1174 1175 1176
    Key key,
    justifyContent: FlexJustifyContent.start,
    alignItems: FlexAlignItems.center,
    textBaseline
1177
  }) : super(children, key: key, direction: FlexDirection.vertical, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline);
1178 1179
}

Adam Barth's avatar
Adam Barth committed
1180 1181 1182 1183 1184 1185
/// Controls how a child of a [Flex], [Row], or [Column] flexes.
///
/// This widget must be a descendant of a [Flex], [Row], or [Column], and the
/// path from this widget to its enclosing [Flex], [Row], or [Column] must
/// contain only components (e.g., not other kinds of widgets, like
/// [RenderObjectWidget]s).
1186
class Flexible extends ParentDataWidget {
1187 1188 1189
  Flexible({ Key key, this.flex: 1, Widget child })
    : super(key: key, child: child);

Adam Barth's avatar
Adam Barth committed
1190 1191 1192 1193 1194 1195
  /// The flex factor to use for this child
  ///
  /// If null, the child is inflexible and determines its own size. If non-null,
  /// the child is flexible and its extent in the main axis is determined by
  /// dividing the free space (after placing the inflexible children)
  /// according to the flex factors of the flexible children.
1196 1197 1198 1199
  final int flex;

  void debugValidateAncestor(Widget ancestor) {
    assert(() {
1200
      'Flexible must placed inside a Flex';
1201 1202 1203
      return ancestor is Flex;
    });
  }
1204 1205 1206 1207 1208 1209

  void applyParentData(RenderObject renderObject) {
    assert(renderObject.parentData is FlexParentData);
    final FlexParentData parentData = renderObject.parentData;
    if (parentData.flex != flex) {
      parentData.flex = flex;
1210 1211 1212
      AbstractNode targetParent = renderObject.parent;
      if (targetParent is RenderObject)
        targetParent.markNeedsLayout();
1213 1214
    }
  }
1215 1216 1217 1218 1219

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('flex: $flex');
  }
1220 1221
}

1222 1223 1224 1225 1226 1227
/// A raw paragraph of text.
///
/// This class is rarely used directly. Instead, consider using [Text], which
/// integrates with [DefaultTextStyle].
class RawText extends LeafRenderObjectWidget {
  RawText({ Key key, this.text }) : super(key: key) {
1228 1229
    assert(text != null);
  }
1230

1231
  final TextSpan text;
1232

1233
  RenderParagraph createRenderObject() => new RenderParagraph(text);
1234

1235
  void updateRenderObject(RenderParagraph renderObject, RawText oldWidget) {
1236
    renderObject.text = text;
1237 1238 1239
  }
}

1240 1241
/// A convience widget for paragraphs of text with heterogeneous style.
///
Devon Carew's avatar
Devon Carew committed
1242
/// The elements parameter is a recursive list of lists that matches the
1243 1244 1245 1246 1247 1248
/// following grammar:
///
///   `elements ::= "string" | [<text-style> <elements>*]``
///
/// Where "string" is text to display and text-style is an instance of
/// TextStyle. The text-style applies to all of the elements that follow.
1249
class StyledText extends StatelessComponent {
1250
  StyledText({ this.elements, Key key }) : super(key: key) {
1251
    assert(_toSpan(elements) != null);
1252
  }
1253

1254
  /// The recursive list of lists that describes the text and style to paint.
1255 1256
  final dynamic elements;

1257
  TextSpan _toSpan(dynamic element) {
1258
    if (element is String)
1259
      return new PlainTextSpan(element);
1260 1261 1262 1263
    if (element is Iterable) {
      dynamic first = element.first;
      if (first is! TextStyle)
        throw new ArgumentError("First element of Iterable is a ${first.runtimeType} not a TextStyle");
1264
      return new StyledTextSpan(first, element.skip(1).map(_toSpan).toList());
1265 1266
    }
    throw new ArgumentError("Element is ${element.runtimeType} not a String or an Iterable");
1267 1268
  }

1269
  Widget build(BuildContext context) {
1270
    return new RawText(text: _toSpan(elements));
1271 1272 1273
  }
}

1274
/// The text style to apply to descendant [Text] widgets without explicit style.
1275 1276 1277 1278 1279 1280 1281 1282 1283 1284
class DefaultTextStyle extends InheritedWidget {
  DefaultTextStyle({
    Key key,
    this.style,
    Widget child
  }) : super(key: key, child: child) {
    assert(style != null);
    assert(child != null);
  }

1285
  /// The text style to apply.
1286 1287
  final TextStyle style;

1288
  /// The style from the closest instance of this class that encloses the given context.
1289
  static TextStyle of(BuildContext context) {
Hixie's avatar
Hixie committed
1290
    DefaultTextStyle result = context.inheritFromWidgetOfType(DefaultTextStyle);
1291 1292 1293 1294
    return result?.style;
  }

  bool updateShouldNotify(DefaultTextStyle old) => style != old.style;
1295 1296 1297 1298 1299

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    '$style'.split('\n').forEach(description.add);
  }
1300 1301
}

1302 1303 1304 1305
/// A run of text.
///
/// By default, the text will be styled using the closest enclosing
/// [DefaultTextStyle].
1306
class Text extends StatelessComponent {
Hixie's avatar
Hixie committed
1307
  Text(this.data, { Key key, this.style }) : super(key: key) {
1308 1309
    assert(data != null);
  }
1310

1311
  /// The text to display.
1312
  final String data;
1313 1314 1315 1316 1317 1318

  /// If non-null, the style to use for this text.
  ///
  /// If the style's "inherit" property is true, the style will be merged with
  /// the closest enclosing [DefaultTextStyle]. Otherwise, the style will
  /// replace the closest enclosing [DefaultTextStyle].
1319 1320
  final TextStyle style;

1321
  Widget build(BuildContext context) {
1322
    TextSpan text = new PlainTextSpan(data);
1323
    TextStyle combinedStyle;
1324 1325
    if (style == null || style.inherit) {
      combinedStyle = DefaultTextStyle.of(context)?.merge(style) ?? style;
1326 1327 1328 1329
    } else {
      combinedStyle = style;
    }
    if (combinedStyle != null)
Hixie's avatar
Hixie committed
1330
      text = new StyledTextSpan(combinedStyle, <TextSpan>[text]);
1331
    return new RawText(text: text);
1332
  }
1333 1334 1335 1336 1337 1338 1339

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('"$data"');
    if (style != null)
      '$style'.split('\n').forEach(description.add);
  }
1340 1341
}

1342 1343 1344 1345 1346 1347 1348
/// Displays a raw image.
///
/// This widget is rarely used directly. Instead, consider using [AssetImage] or
/// [NetworkImage], depending on whather you wish to display an image from the
/// assert bundle or from the network.
class RawImage extends LeafRenderObjectWidget {
  RawImage({
1349 1350 1351 1352 1353
    Key key,
    this.image,
    this.width,
    this.height,
    this.colorFilter,
1354
    this.fit,
1355
    this.alignment,
1356 1357
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
1358
  }) : super(key: key);
1359

1360
  /// The image to display.
1361
  final ui.Image image;
1362 1363 1364 1365 1366

  /// If non-null, require the image to have this width.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1367
  final double width;
1368 1369 1370 1371 1372

  /// If non-null, require the image to have this height.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1373
  final double height;
1374 1375

  /// If non-null, apply this color filter to the image before painint.
Adam Barth's avatar
Adam Barth committed
1376
  final ColorFilter colorFilter;
1377 1378

  /// How to inscribe the image into the place allocated during layout.
1379
  final ImageFit fit;
1380 1381 1382 1383 1384 1385

  /// How to align the image within its bounds.
  ///
  /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
  /// layout bounds.  An alignment of (1.0, 0.5) aligns the image to the middle
  /// of the right edge of its layout bounds.
1386
  final FractionalOffset alignment;
1387 1388

  /// How to paint any portions of the layout bounds not covered by the image.
1389
  final ImageRepeat repeat;
1390 1391 1392 1393 1394 1395 1396 1397

  /// The center slice for a nine-patch image.
  ///
  /// The region of the image inside the center slice will be stretched both
  /// horizontally and vertically to fit the image into its destination. The
  /// region of the image above and below the center slice will be stretched
  /// only horizontally and the region of the image to the left and right of
  /// the center slice will be stretched only vertically.
1398
  final Rect centerSlice;
1399

1400
  RenderImage createRenderObject() => new RenderImage(
1401 1402 1403 1404 1405
    image: image,
    width: width,
    height: height,
    colorFilter: colorFilter,
    fit: fit,
1406
    alignment: alignment,
1407 1408
    repeat: repeat,
    centerSlice: centerSlice);
1409

1410
  void updateRenderObject(RenderImage renderObject, RawImage oldWidget) {
1411 1412 1413 1414
    renderObject.image = image;
    renderObject.width = width;
    renderObject.height = height;
    renderObject.colorFilter = colorFilter;
1415
    renderObject.alignment = alignment;
1416 1417
    renderObject.fit = fit;
    renderObject.repeat = repeat;
1418
    renderObject.centerSlice = centerSlice;
1419 1420 1421
  }
}

1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432
/// Displays an [ImageResource].
///
/// An image resource differs from an image in that it might yet let be loaded
/// from the underlying storage (e.g., the asset bundle or the network) and it
/// might change over time (e.g., an animated image).
///
/// This widget is rarely used directly. Instead, consider using [AssetImage] or
/// [NetworkImage], depending on whather you wish to display an image from the
/// assert bundle or from the network.
class RawImageResource extends StatefulComponent {
  RawImageResource({
1433 1434 1435 1436 1437
    Key key,
    this.image,
    this.width,
    this.height,
    this.colorFilter,
1438
    this.fit,
1439
    this.alignment,
1440 1441
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
1442 1443 1444
  }) : super(key: key) {
    assert(image != null);
  }
1445

1446
  /// The image to display.
1447
  final ImageResource image;
1448 1449 1450 1451 1452

  /// If non-null, require the image to have this width.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1453
  final double width;
1454 1455 1456 1457 1458

  /// If non-null, require the image to have this height.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1459
  final double height;
1460 1461

  /// If non-null, apply this color filter to the image before painint.
Adam Barth's avatar
Adam Barth committed
1462
  final ColorFilter colorFilter;
1463 1464

  /// How to inscribe the image into the place allocated during layout.
1465
  final ImageFit fit;
1466 1467 1468 1469 1470 1471

  /// How to align the image within its bounds.
  ///
  /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
  /// layout bounds.  An alignment of (1.0, 0.5) aligns the image to the middle
  /// of the right edge of its layout bounds.
1472
  final FractionalOffset alignment;
1473 1474

  /// How to paint any portions of the layout bounds not covered by the image.
1475
  final ImageRepeat repeat;
1476 1477 1478 1479 1480 1481 1482 1483

  /// The center slice for a nine-patch image.
  ///
  /// The region of the image inside the center slice will be stretched both
  /// horizontally and vertically to fit the image into its destination. The
  /// region of the image above and below the center slice will be stretched
  /// only horizontally and the region of the image to the left and right of
  /// the center slice will be stretched only vertically.
1484
  final Rect centerSlice;
1485

1486
  _ImageListenerState createState() => new _ImageListenerState();
1487 1488
}

1489
class _ImageListenerState extends State<RawImageResource> {
1490 1491 1492 1493
  void initState() {
    super.initState();
    config.image.addListener(_handleImageChanged);
  }
1494

1495
  ui.Image _resolvedImage;
1496

1497
  void _handleImageChanged(ui.Image resolvedImage) {
1498 1499
    setState(() {
      _resolvedImage = resolvedImage;
1500 1501 1502
    });
  }

1503 1504 1505
  void dispose() {
    config.image.removeListener(_handleImageChanged);
    super.dispose();
1506 1507
  }

1508
  void didUpdateConfig(RawImageResource oldConfig) {
1509 1510 1511 1512
    if (config.image != oldConfig.image) {
      oldConfig.image.removeListener(_handleImageChanged);
      config.image.addListener(_handleImageChanged);
    }
1513 1514
  }

1515
  Widget build(BuildContext context) {
1516
    return new RawImage(
1517
      image: _resolvedImage,
1518 1519 1520 1521
      width: config.width,
      height: config.height,
      colorFilter: config.colorFilter,
      fit: config.fit,
1522
      alignment: config.alignment,
1523 1524
      repeat: config.repeat,
      centerSlice: config.centerSlice
1525
    );
1526 1527 1528
  }
}

1529
/// Displays an image loaded from the network.
1530
class NetworkImage extends StatelessComponent {
1531 1532 1533 1534 1535 1536
  NetworkImage({
    Key key,
    this.src,
    this.width,
    this.height,
    this.colorFilter,
1537
    this.fit,
1538
    this.alignment,
1539 1540
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
1541
  }) : super(key: key);
1542

1543
  /// The URL from which to load the image.
1544
  final String src;
1545 1546 1547 1548 1549

  /// If non-null, require the image to have this width.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1550
  final double width;
1551 1552 1553 1554 1555

  /// If non-null, require the image to have this height.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1556
  final double height;
1557 1558

  /// If non-null, apply this color filter to the image before painint.
Adam Barth's avatar
Adam Barth committed
1559
  final ColorFilter colorFilter;
1560 1561

  /// How to inscribe the image into the place allocated during layout.
1562
  final ImageFit fit;
1563 1564 1565 1566 1567 1568

  /// How to align the image within its bounds.
  ///
  /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
  /// layout bounds.  An alignment of (1.0, 0.5) aligns the image to the middle
  /// of the right edge of its layout bounds.
1569
  final FractionalOffset alignment;
1570 1571

  /// How to paint any portions of the layout bounds not covered by the image.
1572
  final ImageRepeat repeat;
1573 1574 1575 1576 1577 1578 1579 1580

  /// The center slice for a nine-patch image.
  ///
  /// The region of the image inside the center slice will be stretched both
  /// horizontally and vertically to fit the image into its destination. The
  /// region of the image above and below the center slice will be stretched
  /// only horizontally and the region of the image to the left and right of
  /// the center slice will be stretched only vertically.
1581
  final Rect centerSlice;
1582

1583
  Widget build(BuildContext context) {
1584
    return new RawImageResource(
1585
      image: imageCache.load(src),
1586 1587
      width: width,
      height: height,
1588 1589
      colorFilter: colorFilter,
      fit: fit,
1590
      alignment: alignment,
1591 1592
      repeat: repeat,
      centerSlice: centerSlice
1593 1594 1595 1596
    );
  }
}

1597 1598 1599 1600
/// Sets a default asset bundle for its descendants.
///
/// For example, used by [AssetImage] to determine which bundle to use if no
/// bundle is specified explicitly.
Adam Barth's avatar
Adam Barth committed
1601 1602 1603 1604 1605 1606 1607 1608 1609 1610
class DefaultAssetBundle extends InheritedWidget {
  DefaultAssetBundle({
    Key key,
    this.bundle,
    Widget child
  }) : super(key: key, child: child) {
    assert(bundle != null);
    assert(child != null);
  }

1611
  /// The bundle to use as a default.
Adam Barth's avatar
Adam Barth committed
1612 1613
  final AssetBundle bundle;

1614
  /// The bundle from the closest instance of this class that encloses the given context.
Adam Barth's avatar
Adam Barth committed
1615
  static AssetBundle of(BuildContext context) {
Hixie's avatar
Hixie committed
1616
    DefaultAssetBundle result = context.inheritFromWidgetOfType(DefaultAssetBundle);
Adam Barth's avatar
Adam Barth committed
1617 1618 1619 1620 1621 1622
    return result?.bundle;
  }

  bool updateShouldNotify(DefaultAssetBundle old) => bundle != old.bundle;
}

1623 1624 1625 1626 1627
/// Displays an image provided by an [ImageProvider].
///
/// This widget lets you customize how images are loaded by supplying your own
/// image provider. Internally, [NetworkImage] uses an [ImageProvider] that
/// loads the image from the network.
1628 1629
class AsyncImage extends StatelessComponent {
  AsyncImage({
Ali Ghassemi's avatar
Ali Ghassemi committed
1630
    Key key,
1631
    this.provider,
Ali Ghassemi's avatar
Ali Ghassemi committed
1632 1633 1634 1635
    this.width,
    this.height,
    this.colorFilter,
    this.fit,
1636
    this.alignment,
Ali Ghassemi's avatar
Ali Ghassemi committed
1637 1638 1639
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
  }) : super(key: key);
1640

1641
  /// The object that will provide the image.
1642
  final ImageProvider provider;
1643 1644 1645 1646 1647

  /// If non-null, require the image to have this width.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1648
  final double width;
1649 1650 1651 1652 1653

  /// If non-null, require the image to have this height.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1654
  final double height;
1655 1656

  /// If non-null, apply this color filter to the image before painint.
1657
  final ColorFilter colorFilter;
1658 1659

  /// How to inscribe the image into the place allocated during layout.
1660
  final ImageFit fit;
1661 1662 1663 1664 1665 1666

  /// How to align the image within its bounds.
  ///
  /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
  /// layout bounds.  An alignment of (1.0, 0.5) aligns the image to the middle
  /// of the right edge of its layout bounds.
1667
  final FractionalOffset alignment;
1668 1669

  /// How to paint any portions of the layout bounds not covered by the image.
1670
  final ImageRepeat repeat;
1671 1672 1673 1674 1675 1676 1677 1678

  /// The center slice for a nine-patch image.
  ///
  /// The region of the image inside the center slice will be stretched both
  /// horizontally and vertically to fit the image into its destination. The
  /// region of the image above and below the center slice will be stretched
  /// only horizontally and the region of the image to the left and right of
  /// the center slice will be stretched only vertically.
1679 1680 1681
  final Rect centerSlice;

  Widget build(BuildContext context) {
1682
    return new RawImageResource(
1683
      image: imageCache.loadProvider(provider),
Ali Ghassemi's avatar
Ali Ghassemi committed
1684 1685 1686 1687
      width: width,
      height: height,
      colorFilter: colorFilter,
      fit: fit,
1688
      alignment: alignment,
Ali Ghassemi's avatar
Ali Ghassemi committed
1689 1690 1691
      repeat: repeat,
      centerSlice: centerSlice
    );
1692 1693 1694
  }
}

1695 1696 1697 1698
/// Displays an image from an [AssetBundle].
///
/// By default, asset image will load the image from the cloest enclosing
/// [DefaultAssetBundle].
1699
class AssetImage extends StatelessComponent {
1700 1701 1702
  // Don't add asserts here unless absolutely necessary, since it will
  // require removing the const constructor, which is an API change.
  const AssetImage({
1703 1704 1705 1706 1707 1708
    Key key,
    this.name,
    this.bundle,
    this.width,
    this.height,
    this.colorFilter,
1709
    this.fit,
1710
    this.alignment,
1711 1712
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
1713
  }) : super(key: key);
1714

1715
  /// The name of the image in the assert bundle.
1716
  final String name;
1717 1718 1719 1720 1721

  /// The bundle from which to load the image.
  ///
  /// If null, the image will be loaded from the closest enclosing
  /// [DefaultAssetBundle].
1722
  final AssetBundle bundle;
1723 1724 1725 1726 1727

  /// If non-null, require the image to have this width.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1728
  final double width;
1729 1730 1731 1732 1733

  /// If non-null, require the image to have this height.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
1734
  final double height;
1735 1736

  /// If non-null, apply this color filter to the image before painint.
Adam Barth's avatar
Adam Barth committed
1737
  final ColorFilter colorFilter;
1738 1739

  /// How to inscribe the image into the place allocated during layout.
1740
  final ImageFit fit;
1741 1742 1743 1744 1745 1746

  /// How to align the image within its bounds.
  ///
  /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
  /// layout bounds.  An alignment of (1.0, 0.5) aligns the image to the middle
  /// of the right edge of its layout bounds.
1747
  final FractionalOffset alignment;
1748 1749

  /// How to paint any portions of the layout bounds not covered by the image.
1750
  final ImageRepeat repeat;
1751 1752 1753 1754 1755 1756 1757 1758

  /// The center slice for a nine-patch image.
  ///
  /// The region of the image inside the center slice will be stretched both
  /// horizontally and vertically to fit the image into its destination. The
  /// region of the image above and below the center slice will be stretched
  /// only horizontally and the region of the image to the left and right of
  /// the center slice will be stretched only vertically.
1759
  final Rect centerSlice;
1760

1761
  Widget build(BuildContext context) {
1762
    return new RawImageResource(
Adam Barth's avatar
Adam Barth committed
1763
      image: (bundle ?? DefaultAssetBundle.of(context)).loadImage(name),
1764 1765
      width: width,
      height: height,
1766 1767
      colorFilter: colorFilter,
      fit: fit,
1768
      alignment: alignment,
1769 1770
      repeat: repeat,
      centerSlice: centerSlice
1771 1772 1773 1774
    );
  }
}

1775 1776 1777 1778 1779
/// An adapter for placing a specific [RenderBox] in the widget tree.
///
/// A given render object can be placed at most once in the widget tree. This
/// widget enforces that restriction by keying itself using a [GlobalObjectKey]
/// for the given render object.
Hixie's avatar
Hixie committed
1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790
class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
  WidgetToRenderBoxAdapter(RenderBox renderBox)
    : renderBox = renderBox,
      // WidgetToRenderBoxAdapter objects are keyed to their render box. This
      // prevents the widget being used in the widget hierarchy in two different
      // places, which would cause the RenderBox to get inserted in multiple
      // places in the RenderObject tree.
      super(key: new GlobalObjectKey(renderBox)) {
    assert(renderBox != null);
  }

1791
  /// The render box to place in the widget tree.
Hixie's avatar
Hixie committed
1792 1793 1794 1795 1796
  final RenderBox renderBox;

  RenderBox createRenderObject() => renderBox;
}

1797

1798
// EVENT HANDLING
1799

1800 1801 1802 1803 1804 1805 1806
class Listener extends OneChildRenderObjectWidget {
  Listener({
    Key key,
    Widget child,
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
1807 1808 1809 1810 1811
    this.onPointerCancel,
    this.behavior: HitTestBehavior.deferToChild
  }) : super(key: key, child: child) {
    assert(behavior != null);
  }
1812

Ian Hickson's avatar
Ian Hickson committed
1813 1814 1815 1816
  final PointerDownEventListener onPointerDown;
  final PointerMoveEventListener onPointerMove;
  final PointerUpEventListener onPointerUp;
  final PointerCancelEventListener onPointerCancel;
1817
  final HitTestBehavior behavior;
1818 1819 1820 1821 1822

  RenderPointerListener createRenderObject() => new RenderPointerListener(
    onPointerDown: onPointerDown,
    onPointerMove: onPointerMove,
    onPointerUp: onPointerUp,
1823 1824
    onPointerCancel: onPointerCancel,
    behavior: behavior
1825
  );
1826

1827 1828 1829 1830 1831
  void updateRenderObject(RenderPointerListener renderObject, Listener oldWidget) {
    renderObject.onPointerDown = onPointerDown;
    renderObject.onPointerMove = onPointerMove;
    renderObject.onPointerUp = onPointerUp;
    renderObject.onPointerCancel = onPointerCancel;
1832
    renderObject.behavior = behavior;
1833
  }
1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    List<String> listeners = <String>[];
    if (onPointerDown != null)
      listeners.add('down');
    if (onPointerMove != null)
      listeners.add('move');
    if (onPointerUp != null)
      listeners.add('up');
    if (onPointerCancel != null)
      listeners.add('cancel');
    if (listeners.isEmpty)
      listeners.add('<none>');
    description.add('listeners: ${listeners.join(", ")}');
    switch (behavior) {
      case HitTestBehavior.translucent:
        description.add('behavior: translucent');
        break;
      case HitTestBehavior.opaque:
        description.add('behavior: opaque');
        break;
      case HitTestBehavior.deferToChild:
        description.add('behavior: defer-to-child');
        break;
    }
  }
1861
}
1862

1863 1864 1865
class RepaintBoundary extends OneChildRenderObjectWidget {
  RepaintBoundary({ Key key, Widget child }) : super(key: key, child: child);
  RenderRepaintBoundary createRenderObject() => new RenderRepaintBoundary();
1866 1867
}

1868
class IgnorePointer extends OneChildRenderObjectWidget {
1869
  IgnorePointer({ Key key, Widget child, this.ignoring: true })
1870
    : super(key: key, child: child);
1871 1872

  final bool ignoring;
1873

1874
  RenderIgnorePointer createRenderObject() => new RenderIgnorePointer(ignoring: ignoring);
1875

1876
  void updateRenderObject(RenderIgnorePointer renderObject, IgnorePointer oldWidget) {
1877 1878
    renderObject.ignoring = ignoring;
  }
1879
}
1880

1881 1882 1883

// UTILITY NODES

1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894
class MetaData extends OneChildRenderObjectWidget {
  MetaData({ Key key, Widget child, this.metaData })
    : super(key: key, child: child);

  final dynamic metaData;

  RenderMetaData createRenderObject() => new RenderMetaData(metaData: metaData);

  void updateRenderObject(RenderMetaData renderObject, MetaData oldWidget) {
    renderObject.metaData = metaData;
  }
1895 1896 1897 1898 1899

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('$metaData');
  }
1900
}
1901 1902 1903 1904 1905 1906 1907 1908

class KeyedSubtree extends StatelessComponent {
  KeyedSubtree({ Key key, this.child })
    : super(key: key);

  final Widget child;

  Widget build(BuildContext context) => child;
Adam Barth's avatar
Adam Barth committed
1909
}
1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924

class Builder extends StatelessComponent {
  Builder({ Key key, this.builder }) : super(key: key);
  final WidgetBuilder builder;
  Widget build(BuildContext context) => builder(context);
}

typedef Widget StatefulWidgetBuilder(BuildContext context, StateSetter setState);
class StatefulBuilder extends StatefulComponent {
  StatefulBuilder({ Key key, this.builder }) : super(key: key);
  final StatefulWidgetBuilder builder;
  _StatefulBuilderState createState() => new _StatefulBuilderState();
}
class _StatefulBuilderState extends State<StatefulBuilder> {
  Widget build(BuildContext context) => config.builder(context, setState);
1925
}