viewport.dart 6.04 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
import 'package:vector_math/vector_math_64.dart';
8

9 10 11
import 'box.dart';
import 'object.dart';

12 13 14 15
/// The direction in which to scroll
enum ScrollDirection {
  /// Scroll left and right
  horizontal,
16

17 18 19 20 21 22 23
  /// Scroll up and down
  vertical,

  /// Scroll in all four cardinal directions
  both
}

24
/// A render object that's bigger on the inside.
25
///
26 27 28 29 30 31 32
/// 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.
33 34 35 36
class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox> {

  RenderViewport({
    RenderBox child,
Hixie's avatar
Hixie committed
37
    Offset scrollOffset: Offset.zero,
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
    ScrollDirection scrollDirection: ScrollDirection.vertical
  }) : _scrollOffset = scrollOffset,
       _scrollDirection = scrollDirection {
    assert(_offsetIsSane(scrollOffset, scrollDirection));
    this.child = child;
  }

  bool _offsetIsSane(Offset offset, ScrollDirection direction) {
    switch (direction) {
      case ScrollDirection.both:
        return true;
      case ScrollDirection.horizontal:
        return offset.dy == 0.0;
      case ScrollDirection.vertical:
        return offset.dx == 0.0;
    }
  }

56
  /// The offset at which to paint the child.
57 58
  ///
  /// The offset can be non-zero only in the [scrollDirection].
59
  Offset get scrollOffset => _scrollOffset;
60
  Offset _scrollOffset;
61 62 63 64 65 66 67 68
  void set scrollOffset(Offset value) {
    if (value == _scrollOffset)
      return;
    assert(_offsetIsSane(value, scrollDirection));
    _scrollOffset = value;
    markNeedsPaint();
  }

69
  /// The direction in which the child is permitted to be larger than the viewport
70 71 72 73
  ///
  /// 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).
74
  ScrollDirection get scrollDirection => _scrollDirection;
75
  ScrollDirection _scrollDirection;
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  void set scrollDirection(ScrollDirection value) {
    if (value == _scrollDirection)
      return;
    assert(_offsetIsSane(scrollOffset, value));
    _scrollDirection = value;
    markNeedsLayout();
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
    BoxConstraints innerConstraints;
    switch (scrollDirection) {
      case ScrollDirection.both:
        innerConstraints = new BoxConstraints();
        break;
      case ScrollDirection.horizontal:
        innerConstraints = constraints.heightConstraints();
        break;
      case ScrollDirection.vertical:
        innerConstraints = constraints.widthConstraints();
        break;
    }
    return innerConstraints;
  }

  double getMinIntrinsicWidth(BoxConstraints constraints) {
101
    assert(constraints.isNormalized);
102 103 104 105 106 107
    if (child != null)
      return child.getMinIntrinsicWidth(_getInnerConstraints(constraints));
    return super.getMinIntrinsicWidth(constraints);
  }

  double getMaxIntrinsicWidth(BoxConstraints constraints) {
108
    assert(constraints.isNormalized);
109 110 111 112 113 114
    if (child != null)
      return child.getMaxIntrinsicWidth(_getInnerConstraints(constraints));
    return super.getMaxIntrinsicWidth(constraints);
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
115
    assert(constraints.isNormalized);
116 117 118 119 120 121
    if (child != null)
      return child.getMinIntrinsicHeight(_getInnerConstraints(constraints));
    return super.getMinIntrinsicHeight(constraints);
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
122
    assert(constraints.isNormalized);
123 124 125 126 127 128 129 130 131 132 133 134 135 136
    if (child != null)
      return child.getMaxIntrinsicHeight(_getInnerConstraints(constraints));
    return super.getMaxIntrinsicHeight(constraints);
  }

  // We don't override computeDistanceToActualBaseline(), because we
  // want the default behaviour (returning null). Otherwise, as you
  // scroll the RenderViewport, it would shift in its parent if the
  // parent was baseline-aligned, which makes no sense.

  void performLayout() {
    if (child != null) {
      child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
      size = constraints.constrain(child.size);
Hixie's avatar
Hixie committed
137 138
      final BoxParentData childParentData = child.parentData;
      childParentData.position = Point.origin;
139 140 141 142 143 144
    } else {
      performResize();
    }
  }

  Offset get _scrollOffsetRoundedToIntegerDevicePixels {
145
    double devicePixelRatio = ui.window.devicePixelRatio;
146 147 148 149 150 151 152 153 154
    int dxInDevicePixels = (scrollOffset.dx * devicePixelRatio).round();
    int dyInDevicePixels = (scrollOffset.dy * devicePixelRatio).round();
    return new Offset(dxInDevicePixels / devicePixelRatio,
                      dyInDevicePixels / devicePixelRatio);
  }

  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      Offset roundedScrollOffset = _scrollOffsetRoundedToIntegerDevicePixels;
Adam Barth's avatar
Adam Barth committed
155 156 157 158 159 160 161 162 163
      bool _needsClip = offset < Offset.zero
          || !(offset & size).contains(((offset - roundedScrollOffset) & child.size).bottomRight);
      if (_needsClip) {
        context.pushClipRect(needsCompositing, offset, Point.origin & size, (PaintingContext context, Offset offset) {
          context.paintChild(child, offset - roundedScrollOffset);
        });
      } else {
        context.paintChild(child, offset - roundedScrollOffset);
      }
164 165 166
    }
  }

167
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
168
    transform.translate(-scrollOffset.dx, -scrollOffset.dy);
169
    super.applyPaintTransform(child, transform);
170 171
  }

Adam Barth's avatar
Adam Barth committed
172
  bool hitTestChildren(HitTestResult result, { Point position }) {
173 174 175
    if (child != null) {
      assert(child.parentData is BoxParentData);
      Point transformed = position + _scrollOffsetRoundedToIntegerDevicePixels;
Adam Barth's avatar
Adam Barth committed
176
      return child.hitTest(result, position: transformed);
177
    }
Adam Barth's avatar
Adam Barth committed
178
    return false;
179 180
  }
}