basic.dart 61.5 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 {
273
  Transform({ Key key, this.transform, this.origin, this.alignment, this.transformHitTests: true, 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

294 295 296 297 298 299 300 301 302
  /// Whether to apply the translation when performing hit tests.
  final bool transformHitTests;

  RenderTransform createRenderObject() => new RenderTransform(
    transform: transform,
    origin: origin,
    alignment: alignment,
    transformHitTests: transformHitTests
  );
303

304
  void updateRenderObject(RenderTransform renderObject, Transform oldWidget) {
305
    renderObject.transform = transform;
306
    renderObject.origin = origin;
Hixie's avatar
Hixie committed
307
    renderObject.alignment = alignment;
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
    renderObject.transformHitTests = transformHitTests;
  }
}

/// Applies a translation expressed as a fraction of the box's size before
/// painting its child.
class FractionalTranslation extends OneChildRenderObjectWidget {
  FractionalTranslation({ Key key, this.translation, this.transformHitTests: true, Widget child })
    : super(key: key, child: child) {
    assert(translation != null);
  }

  /// The offset by which to translate the child, as a multiple of its size.
  final FractionalOffset translation;

  /// Whether to apply the translation when performing hit tests.
  final bool transformHitTests;

  RenderFractionalTranslation createRenderObject() => new RenderFractionalTranslation(translation: translation, transformHitTests: transformHitTests);

  void updateRenderObject(RenderFractionalTranslation renderObject, FractionalTranslation oldWidget) {
    renderObject.translation = translation;
    renderObject.transformHitTests = transformHitTests;
331 332 333
  }
}

334 335 336 337 338 339
/// 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.
340
class Padding extends OneChildRenderObjectWidget {
341
  Padding({ Key key, this.padding, Widget child })
342 343 344
    : super(key: key, child: child) {
    assert(padding != null);
  }
345

346
  /// The amount to pad the child in each dimension.
347 348
  final EdgeDims padding;

349
  RenderPadding createRenderObject() => new RenderPadding(padding: padding);
350

351
  void updateRenderObject(RenderPadding renderObject, Padding oldWidget) {
352
    renderObject.padding = padding;
353 354 355
  }
}

356 357 358 359 360
/// 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.
361
class Align extends OneChildRenderObjectWidget {
362 363
  Align({
    Key key,
364
    this.alignment: const FractionalOffset(0.5, 0.5),
365 366
    this.widthFactor,
    this.heightFactor,
367
    Widget child
368
  }) : super(key: key, child: child) {
369
    assert(alignment != null && alignment.dx != null && alignment.dy != null);
370 371 372
    assert(widthFactor == null || widthFactor >= 0.0);
    assert(heightFactor == null || heightFactor >= 0.0);
  }
373

374 375 376 377 378 379 380 381 382
  /// 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.
383
  final FractionalOffset alignment;
384 385 386 387

  /// 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.
388
  final double widthFactor;
389 390 391 392

  /// 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.
393
  final double heightFactor;
394

395
  RenderPositionedBox createRenderObject() => new RenderPositionedBox(alignment: alignment, widthFactor: widthFactor, heightFactor: heightFactor);
396

397
  void updateRenderObject(RenderPositionedBox renderObject, Align oldWidget) {
398
    renderObject.alignment = alignment;
399 400
    renderObject.widthFactor = widthFactor;
    renderObject.heightFactor = heightFactor;
401 402 403
  }
}

404
/// Centers its child within itself.
405
class Center extends Align {
406 407
  Center({ Key key, widthFactor, heightFactor, Widget child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
408 409
}

410 411 412 413 414 415
/// 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
416 417 418 419 420 421 422 423 424 425 426 427 428 429
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) {
430 431 432 433
    renderObject.delegate = delegate;
  }
}

434
/// Metadata for identifying children in a [CustomMultiChildLayout].
435
class LayoutId extends ParentDataWidget<CustomMultiChildLayout> {
436 437 438
  LayoutId({
    Key key,
    Widget child,
439 440
    Object id
  }) : id = id, super(key: key ?? new ValueKey(id), child: child) {
441
    assert(child != null);
442
    assert(id != null);
443
  }
444

445
  /// An object representing the identity of this child.
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
  final Object id;

  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');
  }
}

465 466 467 468 469 470
/// 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.
471 472 473
class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
  CustomMultiChildLayout(List<Widget> children, {
    Key key,
474
    this.delegate
475 476 477 478
  }) : super(key: key, children: children) {
    assert(delegate != null);
  }

479
  /// The delegate that controls the layout of the children.
480 481 482 483 484 485 486
  final MultiChildLayoutDelegate delegate;

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

  void updateRenderObject(RenderCustomMultiChildLayoutBox renderObject, CustomMultiChildLayout oldWidget) {
Adam Barth's avatar
Adam Barth committed
487 488 489 490
    renderObject.delegate = delegate;
  }
}

491 492 493 494
/// 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.
495
class SizedBox extends OneChildRenderObjectWidget {
496
  SizedBox({ Key key, this.width, this.height, Widget child })
497 498
    : super(key: key, child: child);

499
  /// If non-null, requires the child to have exactly this width.
500
  final double width;
501 502

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

505 506 507
  RenderConstrainedBox createRenderObject() => new RenderConstrainedBox(
    additionalConstraints: _additionalConstraints
  );
508

509
  BoxConstraints get _additionalConstraints {
510 511
    BoxConstraints result = const BoxConstraints();
    if (width != null)
512
      result = result.tightenWidth(width);
513
    if (height != null)
514
      result = result.tightenHeight(height);
515 516 517
    return result;
  }

518 519
  void updateRenderObject(RenderConstrainedBox renderObject, SizedBox oldWidget) {
    renderObject.additionalConstraints = _additionalConstraints;
520
  }
521 522 523 524 525 526 527 528

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

531 532 533 534 535
/// 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].
536
class ConstrainedBox extends OneChildRenderObjectWidget {
537
  ConstrainedBox({ Key key, this.constraints, Widget child })
538 539 540
    : super(key: key, child: child) {
    assert(constraints != null);
  }
541

542
  /// The additional constraints to impose on the child.
543 544
  final BoxConstraints constraints;

545
  RenderConstrainedBox createRenderObject() => new RenderConstrainedBox(additionalConstraints: constraints);
546

547
  void updateRenderObject(RenderConstrainedBox renderObject, ConstrainedBox oldWidget) {
548
    renderObject.additionalConstraints = constraints;
549
  }
550 551 552

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
Hixie's avatar
Hixie committed
553
    description.add('$constraints');
554
  }
555 556
}

557 558 559
/// Sizes itself to a fraction of the total available space.
///
/// See [RenderFractionallySizedBox] for details.
Hixie's avatar
Hixie committed
560 561 562 563
class FractionallySizedBox extends OneChildRenderObjectWidget {
  FractionallySizedBox({ Key key, this.width, this.height, Widget child })
    : super(key: key, child: child);

564 565 566 567
  /// 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
568
  final double width;
569 570 571 572 573

  /// 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
574 575 576 577 578 579 580
  final double height;

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

581
  void updateRenderObject(RenderFractionallySizedBox renderObject, FractionallySizedBox oldWidget) {
Hixie's avatar
Hixie committed
582 583 584
    renderObject.widthFactor = width;
    renderObject.heightFactor = height;
  }
585 586 587 588 589 590 591 592

  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
593 594
}

595 596 597 598
/// 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.
599 600 601 602
class OverflowBox extends OneChildRenderObjectWidget {
  OverflowBox({ Key key, this.minWidth, this.maxWidth, this.minHeight, this.maxHeight, Widget child })
    : super(key: key, child: child);

603 604
  /// The minimum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
605
  final double minWidth;
606 607 608

  /// The maximum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
609
  final double maxWidth;
610 611 612

  /// The minimum height constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
613
  final double minHeight;
614 615 616

  /// The maximum height constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
  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;
  }
}

634 635 636 637 638 639 640 641 642 643 644 645 646
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;
  }
}

647 648 649
/// 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
650 651 652 653 654 655 656
class OffStage extends OneChildRenderObjectWidget {
  OffStage({ Key key, Widget child })
    : super(key: key, child: child);

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

657 658 659
/// Forces child to layout at a specific aspect ratio.
///
/// See [RenderAspectRatio] for details.
660
class AspectRatio extends OneChildRenderObjectWidget {
661
  AspectRatio({ Key key, this.aspectRatio, Widget child })
662 663 664
    : super(key: key, child: child) {
    assert(aspectRatio != null);
  }
665

666 667 668 669
  /// 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.
670 671
  final double aspectRatio;

672
  RenderAspectRatio createRenderObject() => new RenderAspectRatio(aspectRatio: aspectRatio);
673

674
  void updateRenderObject(RenderAspectRatio renderObject, AspectRatio oldWidget) {
675
    renderObject.aspectRatio = aspectRatio;
676
  }
677 678 679 680 681

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

684 685 686 687 688 689 690 691 692 693 694 695
/// 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.
696
class IntrinsicWidth extends OneChildRenderObjectWidget {
697
  IntrinsicWidth({ Key key, this.stepWidth, this.stepHeight, Widget child })
698 699
    : super(key: key, child: child);

700
  /// If non-null, force the child's width to be a multiple of this value.
701
  final double stepWidth;
702 703

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

706
  RenderIntrinsicWidth createRenderObject() => new RenderIntrinsicWidth(stepWidth: stepWidth, stepHeight: stepHeight);
707

708
  void updateRenderObject(RenderIntrinsicWidth renderObject, IntrinsicWidth oldWidget) {
709 710
    renderObject.stepWidth = stepWidth;
    renderObject.stepHeight = stepHeight;
711 712 713
  }
}

714 715 716 717 718 719 720
/// 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.
721 722 723
class IntrinsicHeight extends OneChildRenderObjectWidget {
  IntrinsicHeight({ Key key, Widget child }) : super(key: key, child: child);
  RenderIntrinsicHeight createRenderObject() => new RenderIntrinsicHeight();
Hixie's avatar
Hixie committed
724 725
}

726
/// Positions its child vertically according to the child's baseline.
727
class Baseline extends OneChildRenderObjectWidget {
728
  Baseline({ Key key, this.baseline, this.baselineType: TextBaseline.alphabetic, Widget child })
729 730 731 732
    : super(key: key, child: child) {
    assert(baseline != null);
    assert(baselineType != null);
  }
733

734 735 736 737 738
  /// 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.
739 740
  final TextBaseline baselineType;

741
  RenderBaseline createRenderObject() => new RenderBaseline(baseline: baseline, baselineType: baselineType);
742

743
  void updateRenderObject(RenderBaseline renderObject, Baseline oldWidget) {
744 745
    renderObject.baseline = baseline;
    renderObject.baselineType = baselineType;
746 747 748
  }
}

749 750 751 752 753 754 755 756 757
/// 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.
758
class Viewport extends OneChildRenderObjectWidget {
759 760
  Viewport({
    Key key,
761
    this.scrollDirection: ScrollDirection.vertical,
762
    this.scrollOffset: Offset.zero,
763
    Widget child
764 765 766 767
  }) : super(key: key, child: child) {
    assert(scrollDirection != null);
    assert(scrollOffset != null);
  }
768

769 770 771 772 773
  /// 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).
774
  final ScrollDirection scrollDirection;
775 776 777 778

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

781
  RenderViewport createRenderObject() => new RenderViewport(scrollDirection: scrollDirection, scrollOffset: scrollOffset);
782

783
  void updateRenderObject(RenderViewport renderObject, Viewport oldWidget) {
784
    // Order dependency: RenderViewport validates scrollOffset based on scrollDirection.
785
    renderObject.scrollDirection = scrollDirection;
786
    renderObject.scrollOffset = scrollOffset;
787 788 789
  }
}

790 791 792 793
/// 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.
794
class SizeObserver extends OneChildRenderObjectWidget {
795
  SizeObserver({ Key key, this.onSizeChanged, Widget child })
796
    : super(key: key, child: child) {
797
    assert(onSizeChanged != null);
798
  }
799

800
  /// The callback to call whenever the child's layout size changes
801
  final SizeChangedCallback onSizeChanged;
802

803
  RenderSizeObserver createRenderObject() => new RenderSizeObserver(onSizeChanged: onSizeChanged);
804

805
  void updateRenderObject(RenderSizeObserver renderObject, SizeObserver oldWidget) {
806
    renderObject.onSizeChanged = onSizeChanged;
807 808
  }

809
  void didUnmountRenderObject(RenderSizeObserver renderObject) {
810
    renderObject.onSizeChanged = null;
811 812 813 814
  }
}


815
// CONTAINER
816

817
/// A convenience widget that combines common painting, positioning, and sizing widgets.
818
class Container extends StatelessComponent {
819
  Container({
820
    Key key,
821 822 823
    this.child,
    this.constraints,
    this.decoration,
824
    this.foregroundDecoration,
825 826
    this.margin,
    this.padding,
Adam Barth's avatar
Adam Barth committed
827 828 829
    this.transform,
    this.width,
    this.height
830 831 832
  }) : super(key: key) {
    assert(margin == null || margin.isNonNegative);
    assert(padding == null || padding.isNonNegative);
833
    assert(decoration == null || decoration.debugAssertValid());
834
  }
835

836 837 838
  /// The child to contain in the container.
  ///
  /// If null, the container will expand to fill all available space in its parent.
839
  final Widget child;
840 841

  /// Additional constraints to apply to the child.
842
  final BoxConstraints constraints;
843 844

  /// The decoration to paint behind the child.
845
  final Decoration decoration;
846 847

  /// The decoration to paint in front of the child.
848
  final Decoration foregroundDecoration;
849 850

  /// Empty space to surround the decoration.
851
  final EdgeDims margin;
852 853

  /// Empty space to inscribe inside the decoration.
854
  final EdgeDims padding;
855

856
  /// The transformation matrix to apply before painting the container.
857
  final Matrix4 transform;
858 859

  /// If non-null, requires the decoration to have this width.
860
  final double width;
861 862

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

865 866
  EdgeDims get _paddingIncludingDecoration {
    if (decoration == null || decoration.padding == null)
867
      return padding;
868
    EdgeDims decorationPadding = decoration.padding;
869
    if (padding == null)
870 871
      return decorationPadding;
    return padding + decorationPadding;
872 873
  }

874
  Widget build(BuildContext context) {
875 876
    Widget current = child;

877
    if (child == null && (width == null || height == null))
878
      current = new ConstrainedBox(constraints: const BoxConstraints.expand());
879

880
    EdgeDims effectivePadding = _paddingIncludingDecoration;
881 882
    if (effectivePadding != null)
      current = new Padding(padding: effectivePadding, child: current);
883 884 885 886

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

887 888 889
    if (foregroundDecoration != null) {
      current = new DecoratedBox(
        decoration: foregroundDecoration,
890
        position: DecorationPosition.foreground,
891 892 893 894
        child: current
      );
    }

895
    if (width != null || height != null) {
896 897 898 899 900
      current = new SizedBox(
        width: width,
        height: height,
        child: current
      );
901
    }
902 903 904 905 906 907 908 909 910 911 912 913 914

    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
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
  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');
  }
934 935 936 937 938
}


// LAYOUT NODES

939 940 941 942 943 944
/// 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].
945
class BlockBody extends MultiChildRenderObjectWidget {
946
  BlockBody(List<Widget> children, {
947 948
    Key key,
    this.direction: BlockDirection.vertical
949 950 951
  }) : super(key: key, children: children) {
    assert(direction != null);
  }
952

953
  /// The direction to use as the main axis.
954 955
  final BlockDirection direction;

956
  RenderBlock createRenderObject() => new RenderBlock(direction: direction);
957

958
  void updateRenderObject(RenderBlock renderObject, BlockBody oldWidget) {
959
    renderObject.direction = direction;
960
  }
961 962
}

963 964 965 966 967 968 969
abstract class StackRenderObjectWidgetBase extends MultiChildRenderObjectWidget {
  StackRenderObjectWidgetBase({
    List<Widget> children,
    Key key
  }) : super(key: key, children: children);
}

Adam Barth's avatar
Adam Barth committed
970 971 972 973
/// 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.
974
class Stack extends StackRenderObjectWidgetBase {
Hans Muller's avatar
Hans Muller committed
975 976
  Stack(List<Widget> children, {
    Key key,
977 978
    this.alignment: const FractionalOffset(0.0, 0.0)
  }) : super(key: key, children: children);
Hans Muller's avatar
Hans Muller committed
979

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

983
  RenderStack createRenderObject() => new RenderStack(alignment: alignment);
Hans Muller's avatar
Hans Muller committed
984 985

  void updateRenderObject(RenderStack renderObject, Stack oldWidget) {
986
    renderObject.alignment = alignment;
Hans Muller's avatar
Hans Muller committed
987 988 989
  }
}

Adam Barth's avatar
Adam Barth committed
990
/// A [Stack] that shows a single child at once.
991
class IndexedStack extends StackRenderObjectWidgetBase {
Hans Muller's avatar
Hans Muller committed
992 993
  IndexedStack(List<Widget> children, {
    Key key,
994
    this.alignment: const FractionalOffset(0.0, 0.0),
Hans Muller's avatar
Hans Muller committed
995
    this.index: 0
996 997 998
  }) : super(key: key, children: children) {
    assert(index != null);
  }
Hans Muller's avatar
Hans Muller committed
999

Adam Barth's avatar
Adam Barth committed
1000
  /// The index of the child to show.
Hans Muller's avatar
Hans Muller committed
1001
  final int index;
Adam Barth's avatar
Adam Barth committed
1002 1003

  /// How to align the non-positioned children in the stack.
1004 1005 1006
  final FractionalOffset alignment;

  RenderIndexedStack createRenderObject() => new RenderIndexedStack(index: index, alignment: alignment);
Hans Muller's avatar
Hans Muller committed
1007 1008 1009 1010

  void updateRenderObject(RenderIndexedStack renderObject, IndexedStack oldWidget) {
    super.updateRenderObject(renderObject, oldWidget);
    renderObject.index = index;
1011
    renderObject.alignment = alignment;
Hans Muller's avatar
Hans Muller committed
1012
  }
1013 1014
}

Adam Barth's avatar
Adam Barth committed
1015 1016 1017 1018 1019
/// 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).
1020
class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
1021 1022 1023 1024 1025 1026
  Positioned({
    Key key,
    Widget child,
    this.top,
    this.right,
    this.bottom,
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
    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,
1043 1044
       right = null,
       bottom = null,
1045
       super(key: key, child: child);
1046

Adam Barth's avatar
Adam Barth committed
1047
  /// The offset of the child's top edge from the top of the stack.
1048
  final double top;
Adam Barth's avatar
Adam Barth committed
1049 1050

  /// The offset of the child's right edge from the right of the stack.
1051
  final double right;
Adam Barth's avatar
Adam Barth committed
1052 1053

  /// The offset of the child's bottom edge from the bottom of the stack.
1054
  final double bottom;
Adam Barth's avatar
Adam Barth committed
1055 1056

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

Adam Barth's avatar
Adam Barth committed
1059 1060 1061
  /// The child's width.
  ///
  /// Ignored if both left and right are non-null.
1062
  final double width;
Adam Barth's avatar
Adam Barth committed
1063 1064 1065 1066

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

1069 1070 1071
  void applyParentData(RenderObject renderObject) {
    assert(renderObject.parentData is StackParentData);
    final StackParentData parentData = renderObject.parentData;
1072
    bool needsLayout = false;
1073

1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
    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;
    }

1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
    if (parentData.width != width) {
      parentData.width = width;
      needsLayout = true;
    }

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

1104 1105 1106 1107 1108
    if (needsLayout) {
      AbstractNode targetParent = renderObject.parent;
      if (targetParent is RenderObject)
        targetParent.markNeedsLayout();
    }
1109
  }
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120

  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');
1121 1122 1123 1124
    if (width != null)
      description.add('width: $width');
    if (height != null)
      description.add('height: $height');
1125
  }
1126 1127
}

Adam Barth's avatar
Adam Barth committed
1128 1129 1130
/// Uses the grid layout algorithm for its children.
///
/// For details about the grid layout algorithm, see [RenderGrid].
1131
class Grid extends MultiChildRenderObjectWidget {
1132
  Grid(List<Widget> children, { Key key, this.maxChildExtent })
1133 1134 1135 1136 1137 1138
    : super(key: key, children: children) {
    assert(maxChildExtent != null);
  }

  final double maxChildExtent;

1139
  RenderGrid createRenderObject() => new RenderGrid(maxChildExtent: maxChildExtent);
1140

1141
  void updateRenderObject(RenderGrid renderObject, Grid oldWidget) {
1142 1143 1144
    renderObject.maxChildExtent = maxChildExtent;
  }
}
1145

Adam Barth's avatar
Adam Barth committed
1146 1147 1148 1149
/// 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.
1150
class Flex extends MultiChildRenderObjectWidget {
1151
  Flex(List<Widget> children, {
1152
    Key key,
1153 1154
    this.direction: FlexDirection.horizontal,
    this.justifyContent: FlexJustifyContent.start,
1155 1156
    this.alignItems: FlexAlignItems.center,
    this.textBaseline
1157 1158 1159 1160 1161
  }) : super(key: key, children: children) {
    assert(direction != null);
    assert(justifyContent != null);
    assert(alignItems != null);
  }
1162 1163 1164 1165

  final FlexDirection direction;
  final FlexJustifyContent justifyContent;
  final FlexAlignItems alignItems;
1166
  final TextBaseline textBaseline;
1167

1168
  RenderFlex createRenderObject() => new RenderFlex(direction: direction, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline);
1169

1170
  void updateRenderObject(RenderFlex renderObject, Flex oldWidget) {
1171 1172 1173 1174
    renderObject.direction = direction;
    renderObject.justifyContent = justifyContent;
    renderObject.alignItems = alignItems;
    renderObject.textBaseline = textBaseline;
1175 1176 1177
  }
}

Adam Barth's avatar
Adam Barth committed
1178 1179 1180 1181
/// 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.
1182
class Row extends Flex {
1183
  Row(List<Widget> children, {
1184 1185 1186 1187
    Key key,
    justifyContent: FlexJustifyContent.start,
    alignItems: FlexAlignItems.center,
    textBaseline
1188
  }) : super(children, key: key, direction: FlexDirection.horizontal, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline);
1189 1190
}

Adam Barth's avatar
Adam Barth committed
1191 1192 1193 1194
/// 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.
1195
class Column extends Flex {
1196
  Column(List<Widget> children, {
1197 1198 1199 1200
    Key key,
    justifyContent: FlexJustifyContent.start,
    alignItems: FlexAlignItems.center,
    textBaseline
1201
  }) : super(children, key: key, direction: FlexDirection.vertical, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline);
1202 1203
}

Adam Barth's avatar
Adam Barth committed
1204 1205 1206 1207 1208 1209
/// 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).
1210
class Flexible extends ParentDataWidget<Flex> {
1211 1212 1213
  Flexible({ Key key, this.flex: 1, Widget child })
    : super(key: key, child: child);

Adam Barth's avatar
Adam Barth committed
1214 1215 1216 1217 1218 1219
  /// 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.
1220 1221
  final int flex;

1222 1223 1224 1225 1226
  void applyParentData(RenderObject renderObject) {
    assert(renderObject.parentData is FlexParentData);
    final FlexParentData parentData = renderObject.parentData;
    if (parentData.flex != flex) {
      parentData.flex = flex;
1227 1228 1229
      AbstractNode targetParent = renderObject.parent;
      if (targetParent is RenderObject)
        targetParent.markNeedsLayout();
1230 1231
    }
  }
1232 1233 1234 1235 1236

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

1239 1240 1241 1242 1243 1244
/// 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) {
1245 1246
    assert(text != null);
  }
1247

1248
  final TextSpan text;
1249

1250
  RenderParagraph createRenderObject() => new RenderParagraph(text);
1251

1252
  void updateRenderObject(RenderParagraph renderObject, RawText oldWidget) {
1253
    renderObject.text = text;
1254 1255 1256
  }
}

1257 1258
/// A convience widget for paragraphs of text with heterogeneous style.
///
Devon Carew's avatar
Devon Carew committed
1259
/// The elements parameter is a recursive list of lists that matches the
1260 1261 1262 1263 1264 1265
/// 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.
1266
class StyledText extends StatelessComponent {
1267
  StyledText({ this.elements, Key key }) : super(key: key) {
1268
    assert(_toSpan(elements) != null);
1269
  }
1270

1271
  /// The recursive list of lists that describes the text and style to paint.
1272 1273
  final dynamic elements;

1274
  TextSpan _toSpan(dynamic element) {
1275
    if (element is String)
1276
      return new PlainTextSpan(element);
1277 1278 1279 1280
    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");
1281
      return new StyledTextSpan(first, element.skip(1).map(_toSpan).toList());
1282 1283
    }
    throw new ArgumentError("Element is ${element.runtimeType} not a String or an Iterable");
1284 1285
  }

1286
  Widget build(BuildContext context) {
1287
    return new RawText(text: _toSpan(elements));
1288 1289 1290
  }
}

1291
/// The text style to apply to descendant [Text] widgets without explicit style.
1292 1293 1294 1295 1296 1297 1298 1299 1300 1301
class DefaultTextStyle extends InheritedWidget {
  DefaultTextStyle({
    Key key,
    this.style,
    Widget child
  }) : super(key: key, child: child) {
    assert(style != null);
    assert(child != null);
  }

1302
  /// The text style to apply.
1303 1304
  final TextStyle style;

1305
  /// The style from the closest instance of this class that encloses the given context.
1306
  static TextStyle of(BuildContext context) {
Hixie's avatar
Hixie committed
1307
    DefaultTextStyle result = context.inheritFromWidgetOfType(DefaultTextStyle);
1308 1309 1310 1311
    return result?.style;
  }

  bool updateShouldNotify(DefaultTextStyle old) => style != old.style;
1312 1313 1314 1315 1316

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

1319 1320 1321 1322
/// A run of text.
///
/// By default, the text will be styled using the closest enclosing
/// [DefaultTextStyle].
1323
class Text extends StatelessComponent {
Hixie's avatar
Hixie committed
1324
  Text(this.data, { Key key, this.style }) : super(key: key) {
1325 1326
    assert(data != null);
  }
1327

1328
  /// The text to display.
1329
  final String data;
1330 1331 1332 1333 1334 1335

  /// 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].
1336 1337
  final TextStyle style;

1338
  Widget build(BuildContext context) {
1339
    TextSpan text = new PlainTextSpan(data);
1340
    TextStyle combinedStyle;
1341 1342
    if (style == null || style.inherit) {
      combinedStyle = DefaultTextStyle.of(context)?.merge(style) ?? style;
1343 1344 1345 1346
    } else {
      combinedStyle = style;
    }
    if (combinedStyle != null)
Hixie's avatar
Hixie committed
1347
      text = new StyledTextSpan(combinedStyle, <TextSpan>[text]);
1348
    return new RawText(text: text);
1349
  }
1350 1351 1352 1353 1354 1355 1356

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

1359 1360 1361 1362 1363 1364 1365
/// 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({
1366 1367 1368 1369 1370
    Key key,
    this.image,
    this.width,
    this.height,
    this.colorFilter,
1371
    this.fit,
1372
    this.alignment,
1373 1374
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
1375
  }) : super(key: key);
1376

1377
  /// The image to display.
1378
  final ui.Image image;
1379 1380 1381 1382 1383

  /// 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.
1384
  final double width;
1385 1386 1387 1388 1389

  /// 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.
1390
  final double height;
1391 1392

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

  /// How to inscribe the image into the place allocated during layout.
1396
  final ImageFit fit;
1397 1398 1399 1400 1401 1402

  /// 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.
1403
  final FractionalOffset alignment;
1404 1405

  /// How to paint any portions of the layout bounds not covered by the image.
1406
  final ImageRepeat repeat;
1407 1408 1409 1410 1411 1412 1413 1414

  /// 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.
1415
  final Rect centerSlice;
1416

1417
  RenderImage createRenderObject() => new RenderImage(
1418 1419 1420 1421 1422
    image: image,
    width: width,
    height: height,
    colorFilter: colorFilter,
    fit: fit,
1423
    alignment: alignment,
1424 1425
    repeat: repeat,
    centerSlice: centerSlice);
1426

1427
  void updateRenderObject(RenderImage renderObject, RawImage oldWidget) {
1428 1429 1430 1431
    renderObject.image = image;
    renderObject.width = width;
    renderObject.height = height;
    renderObject.colorFilter = colorFilter;
1432
    renderObject.alignment = alignment;
1433 1434
    renderObject.fit = fit;
    renderObject.repeat = repeat;
1435
    renderObject.centerSlice = centerSlice;
1436 1437 1438
  }
}

1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449
/// 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({
1450 1451 1452 1453 1454
    Key key,
    this.image,
    this.width,
    this.height,
    this.colorFilter,
1455
    this.fit,
1456
    this.alignment,
1457 1458
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
1459 1460 1461
  }) : super(key: key) {
    assert(image != null);
  }
1462

1463
  /// The image to display.
1464
  final ImageResource image;
1465 1466 1467 1468 1469

  /// 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.
1470
  final double width;
1471 1472 1473 1474 1475

  /// 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.
1476
  final double height;
1477 1478

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

  /// How to inscribe the image into the place allocated during layout.
1482
  final ImageFit fit;
1483 1484 1485 1486 1487 1488

  /// 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.
1489
  final FractionalOffset alignment;
1490 1491

  /// How to paint any portions of the layout bounds not covered by the image.
1492
  final ImageRepeat repeat;
1493 1494 1495 1496 1497 1498 1499 1500

  /// 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.
1501
  final Rect centerSlice;
1502

1503
  _ImageListenerState createState() => new _ImageListenerState();
1504 1505
}

1506
class _ImageListenerState extends State<RawImageResource> {
1507 1508 1509 1510
  void initState() {
    super.initState();
    config.image.addListener(_handleImageChanged);
  }
1511

1512
  ui.Image _resolvedImage;
1513

1514
  void _handleImageChanged(ui.Image resolvedImage) {
1515 1516
    setState(() {
      _resolvedImage = resolvedImage;
1517 1518 1519
    });
  }

1520 1521 1522
  void dispose() {
    config.image.removeListener(_handleImageChanged);
    super.dispose();
1523 1524
  }

1525
  void didUpdateConfig(RawImageResource oldConfig) {
1526 1527 1528 1529
    if (config.image != oldConfig.image) {
      oldConfig.image.removeListener(_handleImageChanged);
      config.image.addListener(_handleImageChanged);
    }
1530 1531
  }

1532
  Widget build(BuildContext context) {
1533
    return new RawImage(
1534
      image: _resolvedImage,
1535 1536 1537 1538
      width: config.width,
      height: config.height,
      colorFilter: config.colorFilter,
      fit: config.fit,
1539
      alignment: config.alignment,
1540 1541
      repeat: config.repeat,
      centerSlice: config.centerSlice
1542
    );
1543 1544 1545
  }
}

1546
/// Displays an image loaded from the network.
1547
class NetworkImage extends StatelessComponent {
1548 1549 1550 1551 1552 1553
  NetworkImage({
    Key key,
    this.src,
    this.width,
    this.height,
    this.colorFilter,
1554
    this.fit,
1555
    this.alignment,
1556 1557
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
1558
  }) : super(key: key);
1559

1560
  /// The URL from which to load the image.
1561
  final String src;
1562 1563 1564 1565 1566

  /// 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.
1567
  final double width;
1568 1569 1570 1571 1572

  /// 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.
1573
  final double height;
1574 1575

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

  /// How to inscribe the image into the place allocated during layout.
1579
  final ImageFit fit;
1580 1581 1582 1583 1584 1585

  /// 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.
1586
  final FractionalOffset alignment;
1587 1588

  /// How to paint any portions of the layout bounds not covered by the image.
1589
  final ImageRepeat repeat;
1590 1591 1592 1593 1594 1595 1596 1597

  /// 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.
1598
  final Rect centerSlice;
1599

1600
  Widget build(BuildContext context) {
1601
    return new RawImageResource(
1602
      image: imageCache.load(src),
1603 1604
      width: width,
      height: height,
1605 1606
      colorFilter: colorFilter,
      fit: fit,
1607
      alignment: alignment,
1608 1609
      repeat: repeat,
      centerSlice: centerSlice
1610 1611 1612 1613
    );
  }
}

1614 1615 1616 1617
/// 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
1618 1619 1620 1621 1622 1623 1624 1625 1626 1627
class DefaultAssetBundle extends InheritedWidget {
  DefaultAssetBundle({
    Key key,
    this.bundle,
    Widget child
  }) : super(key: key, child: child) {
    assert(bundle != null);
    assert(child != null);
  }

1628
  /// The bundle to use as a default.
Adam Barth's avatar
Adam Barth committed
1629 1630
  final AssetBundle bundle;

1631
  /// The bundle from the closest instance of this class that encloses the given context.
Adam Barth's avatar
Adam Barth committed
1632
  static AssetBundle of(BuildContext context) {
Hixie's avatar
Hixie committed
1633
    DefaultAssetBundle result = context.inheritFromWidgetOfType(DefaultAssetBundle);
Adam Barth's avatar
Adam Barth committed
1634 1635 1636 1637 1638 1639
    return result?.bundle;
  }

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

1640 1641 1642 1643 1644
/// 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.
1645 1646
class AsyncImage extends StatelessComponent {
  AsyncImage({
Ali Ghassemi's avatar
Ali Ghassemi committed
1647
    Key key,
1648
    this.provider,
Ali Ghassemi's avatar
Ali Ghassemi committed
1649 1650 1651 1652
    this.width,
    this.height,
    this.colorFilter,
    this.fit,
1653
    this.alignment,
Ali Ghassemi's avatar
Ali Ghassemi committed
1654 1655 1656
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
  }) : super(key: key);
1657

1658
  /// The object that will provide the image.
1659
  final ImageProvider provider;
1660 1661 1662 1663 1664

  /// 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.
1665
  final double width;
1666 1667 1668 1669 1670

  /// 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.
1671
  final double height;
1672 1673

  /// If non-null, apply this color filter to the image before painint.
1674
  final ColorFilter colorFilter;
1675 1676

  /// How to inscribe the image into the place allocated during layout.
1677
  final ImageFit fit;
1678 1679 1680 1681 1682 1683

  /// 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.
1684
  final FractionalOffset alignment;
1685 1686

  /// How to paint any portions of the layout bounds not covered by the image.
1687
  final ImageRepeat repeat;
1688 1689 1690 1691 1692 1693 1694 1695

  /// 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.
1696 1697 1698
  final Rect centerSlice;

  Widget build(BuildContext context) {
1699
    return new RawImageResource(
1700
      image: imageCache.loadProvider(provider),
Ali Ghassemi's avatar
Ali Ghassemi committed
1701 1702 1703 1704
      width: width,
      height: height,
      colorFilter: colorFilter,
      fit: fit,
1705
      alignment: alignment,
Ali Ghassemi's avatar
Ali Ghassemi committed
1706 1707 1708
      repeat: repeat,
      centerSlice: centerSlice
    );
1709 1710 1711
  }
}

1712 1713 1714 1715
/// Displays an image from an [AssetBundle].
///
/// By default, asset image will load the image from the cloest enclosing
/// [DefaultAssetBundle].
1716
class AssetImage extends StatelessComponent {
1717 1718 1719
  // Don't add asserts here unless absolutely necessary, since it will
  // require removing the const constructor, which is an API change.
  const AssetImage({
1720 1721 1722 1723 1724 1725
    Key key,
    this.name,
    this.bundle,
    this.width,
    this.height,
    this.colorFilter,
1726
    this.fit,
1727
    this.alignment,
1728 1729
    this.repeat: ImageRepeat.noRepeat,
    this.centerSlice
1730
  }) : super(key: key);
1731

1732
  /// The name of the image in the assert bundle.
1733
  final String name;
1734 1735 1736 1737 1738

  /// The bundle from which to load the image.
  ///
  /// If null, the image will be loaded from the closest enclosing
  /// [DefaultAssetBundle].
1739
  final AssetBundle bundle;
1740 1741 1742 1743 1744

  /// 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.
1745
  final double width;
1746 1747 1748 1749 1750

  /// 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.
1751
  final double height;
1752 1753

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

  /// How to inscribe the image into the place allocated during layout.
1757
  final ImageFit fit;
1758 1759 1760 1761 1762 1763

  /// 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.
1764
  final FractionalOffset alignment;
1765 1766

  /// How to paint any portions of the layout bounds not covered by the image.
1767
  final ImageRepeat repeat;
1768 1769 1770 1771 1772 1773 1774 1775

  /// 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.
1776
  final Rect centerSlice;
1777

1778
  Widget build(BuildContext context) {
1779
    return new RawImageResource(
Adam Barth's avatar
Adam Barth committed
1780
      image: (bundle ?? DefaultAssetBundle.of(context)).loadImage(name),
1781 1782
      width: width,
      height: height,
1783 1784
      colorFilter: colorFilter,
      fit: fit,
1785
      alignment: alignment,
1786 1787
      repeat: repeat,
      centerSlice: centerSlice
1788 1789 1790 1791
    );
  }
}

1792 1793 1794 1795 1796
/// 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
1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807
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);
  }

1808
  /// The render box to place in the widget tree.
Hixie's avatar
Hixie committed
1809 1810 1811 1812 1813
  final RenderBox renderBox;

  RenderBox createRenderObject() => renderBox;
}

1814

1815
// EVENT HANDLING
1816

1817 1818 1819 1820 1821 1822 1823
class Listener extends OneChildRenderObjectWidget {
  Listener({
    Key key,
    Widget child,
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
1824 1825 1826 1827 1828
    this.onPointerCancel,
    this.behavior: HitTestBehavior.deferToChild
  }) : super(key: key, child: child) {
    assert(behavior != null);
  }
1829

Ian Hickson's avatar
Ian Hickson committed
1830 1831 1832 1833
  final PointerDownEventListener onPointerDown;
  final PointerMoveEventListener onPointerMove;
  final PointerUpEventListener onPointerUp;
  final PointerCancelEventListener onPointerCancel;
1834
  final HitTestBehavior behavior;
1835 1836 1837 1838 1839

  RenderPointerListener createRenderObject() => new RenderPointerListener(
    onPointerDown: onPointerDown,
    onPointerMove: onPointerMove,
    onPointerUp: onPointerUp,
1840 1841
    onPointerCancel: onPointerCancel,
    behavior: behavior
1842
  );
1843

1844 1845 1846 1847 1848
  void updateRenderObject(RenderPointerListener renderObject, Listener oldWidget) {
    renderObject.onPointerDown = onPointerDown;
    renderObject.onPointerMove = onPointerMove;
    renderObject.onPointerUp = onPointerUp;
    renderObject.onPointerCancel = onPointerCancel;
1849
    renderObject.behavior = behavior;
1850
  }
1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877

  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;
    }
  }
1878
}
1879

1880 1881 1882
class RepaintBoundary extends OneChildRenderObjectWidget {
  RepaintBoundary({ Key key, Widget child }) : super(key: key, child: child);
  RenderRepaintBoundary createRenderObject() => new RenderRepaintBoundary();
1883 1884
}

1885
class IgnorePointer extends OneChildRenderObjectWidget {
1886
  IgnorePointer({ Key key, Widget child, this.ignoring: true })
1887
    : super(key: key, child: child);
1888 1889

  final bool ignoring;
1890

1891
  RenderIgnorePointer createRenderObject() => new RenderIgnorePointer(ignoring: ignoring);
1892

1893
  void updateRenderObject(RenderIgnorePointer renderObject, IgnorePointer oldWidget) {
1894 1895
    renderObject.ignoring = ignoring;
  }
1896
}
1897

1898 1899 1900

// UTILITY NODES

1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911
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;
  }
1912 1913 1914 1915 1916

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('$metaData');
  }
1917
}
1918 1919 1920 1921 1922 1923 1924 1925

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
1926
}
1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941

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);
1942
}