// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:sky' as sky; import 'package:sky/rendering/object.dart'; import 'package:sky/rendering/box.dart'; import 'package:vector_math/vector_math.dart'; enum ScrollDirection { horizontal, vertical, both } class RenderViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox> { RenderViewport({ RenderBox child, Offset scrollOffset, 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; } } Offset _scrollOffset; Offset get scrollOffset => _scrollOffset; void set scrollOffset(Offset value) { if (value == _scrollOffset) return; assert(_offsetIsSane(value, scrollDirection)); _scrollOffset = value; markNeedsPaint(); } ScrollDirection _scrollDirection; ScrollDirection get scrollDirection => _scrollDirection; 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) { if (child != null) return child.getMinIntrinsicWidth(_getInnerConstraints(constraints)); return super.getMinIntrinsicWidth(constraints); } double getMaxIntrinsicWidth(BoxConstraints constraints) { if (child != null) return child.getMaxIntrinsicWidth(_getInnerConstraints(constraints)); return super.getMaxIntrinsicWidth(constraints); } double getMinIntrinsicHeight(BoxConstraints constraints) { if (child != null) return child.getMinIntrinsicHeight(_getInnerConstraints(constraints)); return super.getMinIntrinsicHeight(constraints); } double getMaxIntrinsicHeight(BoxConstraints constraints) { 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); assert(child.parentData is BoxParentData); child.parentData.position = Point.origin; } else { performResize(); } } Offset get _scrollOffsetRoundedToIntegerDevicePixels { double devicePixelRatio = sky.view.devicePixelRatio; 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; bool _needsClip = offset < Offset.zero || !(offset & size).contains(((offset - roundedScrollOffset) & child.size).bottomRight); if (_needsClip) context.paintChildWithClipRect(child, (offset - roundedScrollOffset).toPoint(), offset & size); else context.paintChild(child, (offset - roundedScrollOffset).toPoint()); } } void applyPaintTransform(Matrix4 transform) { super.applyPaintTransform(transform); transform.translate(-scrollOffset.dx, -scrollOffset.dy); } void hitTestChildren(HitTestResult result, { Point position }) { if (child != null) { assert(child.parentData is BoxParentData); Point transformed = position + _scrollOffsetRoundedToIntegerDevicePixels; child.hitTest(result, position: transformed); } } }