mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Split layout2.dart into several files
Previously layout2.dart was a thousand lines long. R=ianh@google.com Review URL: https://codereview.chromium.org/1161003002
This commit is contained in:
parent
9f086ea445
commit
8cbfd0a9fa
@ -4,7 +4,10 @@
|
||||
|
||||
import 'dart:sky';
|
||||
import 'package:sky/framework/app.dart';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
import 'package:sky/framework/rendering/render_box.dart';
|
||||
import 'package:sky/framework/rendering/render_node.dart';
|
||||
import 'package:sky/framework/rendering/render_flex.dart';
|
||||
import 'package:sky/framework/rendering/render_paragraph.dart';
|
||||
|
||||
class RenderSolidColor extends RenderDecoratedBox {
|
||||
final Size desiredSize;
|
||||
@ -15,7 +18,7 @@ class RenderSolidColor extends RenderDecoratedBox {
|
||||
super(decoration: new BoxDecoration(backgroundColor: backgroundColor));
|
||||
|
||||
Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
return constraints.constrain(new Size(desiredWidth, desiredHeight));
|
||||
return constraints.constrain(desiredSize);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:sky' as sky;
|
||||
import 'package:sky/framework/app.dart';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
import 'package:sky/framework/rendering/render_box.dart';
|
||||
import 'package:sky/framework/rendering/render_node.dart';
|
||||
|
||||
const double kTwoPi = 2 * math.PI;
|
||||
|
||||
@ -1,87 +0,0 @@
|
||||
// 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/framework/app.dart';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
|
||||
class RenderSolidColor extends RenderDecoratedBox {
|
||||
final sky.Size desiredSize;
|
||||
final int backgroundColor;
|
||||
|
||||
RenderSolidColor(int backgroundColor, { this.desiredSize: const sky.Size.infinite() })
|
||||
: backgroundColor = backgroundColor,
|
||||
super(decoration: new BoxDecoration(backgroundColor: backgroundColor)) {
|
||||
}
|
||||
|
||||
BoxDimensions getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
return new BoxDimensions.withConstraints(constraints,
|
||||
width: desiredSize.width,
|
||||
height: desiredSize.height);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
size = constraints.constrain(desiredSize);
|
||||
}
|
||||
|
||||
void handlePointer(sky.PointerEvent event) {
|
||||
if (event.type == 'pointerdown')
|
||||
decoration = new BoxDecoration(backgroundColor: 0xFFFF0000);
|
||||
else if (event.type == 'pointerup')
|
||||
decoration = new BoxDecoration(backgroundColor: backgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
AppView app;
|
||||
|
||||
void main() {
|
||||
RenderFlex flexRoot = new RenderFlex(direction: FlexDirection.Vertical);
|
||||
|
||||
RenderNode root = new RenderDecoratedBox(
|
||||
decoration: new BoxDecoration(backgroundColor: 0xFF000000),
|
||||
child: flexRoot
|
||||
);
|
||||
|
||||
void addFlexChildSolidColor(RenderFlex parent, int backgroundColor, { int flex: 0 }) {
|
||||
RenderNode child = new RenderSolidColor(backgroundColor);
|
||||
parent.add(child);
|
||||
child.parentData.flex = flex;
|
||||
}
|
||||
|
||||
// Yellow bar at top
|
||||
addFlexChildSolidColor(flexRoot, 0xFFFFFF00, flex: 1);
|
||||
|
||||
// Turquoise box
|
||||
flexRoot.add(new RenderSolidColor(0x7700FFFF, desiredSize: new sky.Size(100.0, 100.0)));
|
||||
|
||||
// Green and cyan render block with padding
|
||||
var renderBlock = new RenderBlock();
|
||||
|
||||
renderBlock.add(new RenderSolidColor(0xFF00FF00, desiredSize: new sky.Size(100.0, 50.0)));
|
||||
renderBlock.add(new RenderSolidColor(0x7700FFFF, desiredSize: new sky.Size(50.0, 100.0)));
|
||||
|
||||
var renderDecoratedBlock = new RenderDecoratedBox(
|
||||
decoration: new BoxDecoration(backgroundColor: 0xFFFFFFFF),
|
||||
child: renderBlock
|
||||
);
|
||||
|
||||
flexRoot.add(new RenderPadding(const EdgeDims(10.0, 10.0, 10.0, 10.0), renderDecoratedBlock));
|
||||
|
||||
var row = new RenderFlex(direction: FlexDirection.Horizontal);
|
||||
|
||||
// Purple and blue cells
|
||||
addFlexChildSolidColor(row, 0x77FF00FF, flex: 1);
|
||||
addFlexChildSolidColor(row, 0xFF0000FF, flex: 2);
|
||||
|
||||
var decoratedRow = new RenderDecoratedBox(
|
||||
decoration: new BoxDecoration(backgroundColor: 0xFF333333),
|
||||
child: row
|
||||
);
|
||||
|
||||
flexRoot.add(decoratedRow);
|
||||
decoratedRow.parentData.flex = 3;
|
||||
|
||||
app = new AppView(root);
|
||||
|
||||
}
|
||||
@ -5,7 +5,8 @@
|
||||
import 'dart:math';
|
||||
import 'dart:sky';
|
||||
import 'package:sky/framework/app.dart';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
import 'package:sky/framework/rendering/render_box.dart';
|
||||
import 'package:sky/framework/rendering/render_node.dart';
|
||||
|
||||
// Material design colors. :p
|
||||
List<int> colors = [
|
||||
|
||||
@ -23,7 +23,6 @@ import 'stock_data.dart';
|
||||
// import 'stock_menu.dart';
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
|
||||
enum StockMode { Optimistic, Pessimistic }
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
|
||||
import 'dart:math';
|
||||
import 'package:sky/framework/fn2.dart';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
|
||||
class StockArrow extends Component {
|
||||
static final Style _style = new Style('''
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/framework/fn2.dart';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
import 'package:sky/framework/components2/popup_menu.dart';
|
||||
import 'package:sky/framework/components2/checkbox.dart';
|
||||
import 'package:sky/framework/theme/view_configuration.dart';
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
|
||||
import 'package:sky/framework/components2/ink_well.dart';
|
||||
import 'package:sky/framework/fn2.dart';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
import 'package:sky/framework/theme/typography.dart' as typography;
|
||||
import 'stock_arrow.dart';
|
||||
import 'stock_data.dart';
|
||||
|
||||
@ -92,10 +92,14 @@ dart_pkg("sdk") {
|
||||
"lib/framework/fn.dart",
|
||||
"lib/framework/fn2.dart",
|
||||
"lib/framework/layout.dart",
|
||||
"lib/framework/layout2.dart",
|
||||
"lib/framework/net/fetch.dart",
|
||||
"lib/framework/node.dart",
|
||||
"lib/framework/reflect.dart",
|
||||
"lib/framework/rendering/render_block.dart",
|
||||
"lib/framework/rendering/render_box.dart",
|
||||
"lib/framework/rendering/render_flex.dart",
|
||||
"lib/framework/rendering/render_node.dart",
|
||||
"lib/framework/rendering/render_paragraph.dart",
|
||||
"lib/framework/shell.dart",
|
||||
"lib/framework/theme/colors.dart",
|
||||
"lib/framework/theme/shadows.dart",
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'layout2.dart';
|
||||
import 'dart:sky' as sky;
|
||||
import 'rendering/render_box.dart';
|
||||
import 'rendering/render_node.dart';
|
||||
|
||||
class AppView {
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../fn2.dart';
|
||||
import '../layout2.dart';
|
||||
import 'button_base.dart';
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../fn2.dart';
|
||||
import '../layout2.dart';
|
||||
import '../theme/colors.dart';
|
||||
import '../theme/view_configuration.dart';
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ import '../fn2.dart';
|
||||
import '../theme/view_configuration.dart' as config;
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import '../layout2.dart';
|
||||
|
||||
const double _kSplashConfirmedDuration = 350.0;
|
||||
const double _kSplashUnconfirmedDuration = config.kDefaultLongPressTimeout;
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../fn2.dart';
|
||||
import '../layout2.dart';
|
||||
import 'dart:collection';
|
||||
import 'dart:sky' as sky;
|
||||
import 'ink_splash.dart';
|
||||
|
||||
@ -6,7 +6,6 @@ import '../editing/editable_string.dart';
|
||||
import '../editing/editable_text.dart';
|
||||
import '../editing/keyboard.dart';
|
||||
import '../fn2.dart';
|
||||
import '../layout2.dart';
|
||||
import '../theme/colors.dart';
|
||||
import '../theme/typography.dart' as typography;
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../fn2.dart';
|
||||
import '../layout2.dart';
|
||||
import 'button_base.dart';
|
||||
import 'icon.dart';
|
||||
import 'ink_well.dart';
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../fn2.dart';
|
||||
import '../layout2.dart';
|
||||
import '../theme/typography.dart' as typography;
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../fn2.dart';
|
||||
import '../layout2.dart';
|
||||
import '../theme/view_configuration.dart';
|
||||
import 'material.dart';
|
||||
|
||||
|
||||
@ -9,8 +9,12 @@ import 'dart:collection';
|
||||
import 'dart:mirrors';
|
||||
import 'dart:sky' as sky;
|
||||
import 'reflect.dart' as reflect;
|
||||
import 'layout2.dart';
|
||||
import 'app.dart';
|
||||
import 'rendering/render_block.dart';
|
||||
import 'rendering/render_box.dart';
|
||||
import 'rendering/render_flex.dart';
|
||||
import 'rendering/render_node.dart';
|
||||
import 'rendering/render_paragraph.dart';
|
||||
|
||||
// final sky.Tracing _tracing = sky.window.tracing;
|
||||
|
||||
|
||||
@ -1,993 +0,0 @@
|
||||
// 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 'node.dart';
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
// ABSTRACT LAYOUT
|
||||
|
||||
class ParentData {
|
||||
void detach() {
|
||||
detachSiblings();
|
||||
}
|
||||
void detachSiblings() { } // workaround for lack of inter-class mixins in Dart
|
||||
void merge(ParentData other) {
|
||||
// override this in subclasses to merge in data from other into this
|
||||
assert(other.runtimeType == this.runtimeType);
|
||||
}
|
||||
}
|
||||
|
||||
const kLayoutDirections = 4;
|
||||
|
||||
double clamp({double min: 0.0, double value: 0.0, double max: double.INFINITY}) {
|
||||
assert(min != null);
|
||||
assert(value != null);
|
||||
assert(max != null);
|
||||
|
||||
if (value > max)
|
||||
value = max;
|
||||
if (value < min)
|
||||
value = min;
|
||||
return value;
|
||||
}
|
||||
|
||||
class RenderNodeDisplayList extends sky.PictureRecorder {
|
||||
RenderNodeDisplayList(double width, double height) : super(width, height);
|
||||
void paintChild(RenderNode child, sky.Point position) {
|
||||
save();
|
||||
translate(position.x, position.y);
|
||||
child.paint(this);
|
||||
restore();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class RenderNode extends AbstractNode {
|
||||
|
||||
// LAYOUT
|
||||
|
||||
// parentData is only for use by the RenderNode that actually lays this
|
||||
// node out, and any other nodes who happen to know exactly what
|
||||
// kind of node that is.
|
||||
ParentData parentData;
|
||||
void setParentData(RenderNode child) {
|
||||
// override this to setup .parentData correctly for your class
|
||||
if (child.parentData is! ParentData)
|
||||
child.parentData = new ParentData();
|
||||
}
|
||||
|
||||
void adoptChild(RenderNode child) { // only for use by subclasses
|
||||
// call this whenever you decide a node is a child
|
||||
assert(child != null);
|
||||
setParentData(child);
|
||||
super.adoptChild(child);
|
||||
}
|
||||
void dropChild(RenderNode child) { // only for use by subclasses
|
||||
assert(child != null);
|
||||
assert(child.parentData != null);
|
||||
child.parentData.detach();
|
||||
super.dropChild(child);
|
||||
}
|
||||
|
||||
static List<RenderNode> _nodesNeedingLayout = new List<RenderNode>();
|
||||
static bool _debugDoingLayout = false;
|
||||
bool _needsLayout = true;
|
||||
bool get needsLayout => _needsLayout;
|
||||
RenderNode _relayoutSubtreeRoot;
|
||||
dynamic _constraints;
|
||||
dynamic get constraints => _constraints;
|
||||
bool debugAncestorsAlreadyMarkedNeedsLayout() {
|
||||
if (_relayoutSubtreeRoot == null)
|
||||
return true; // we haven't yet done layout even once, so there's nothing for us to do
|
||||
RenderNode node = this;
|
||||
while (node != _relayoutSubtreeRoot) {
|
||||
assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot);
|
||||
assert(node.parent != null);
|
||||
node = node.parent as RenderNode;
|
||||
if (!node._needsLayout)
|
||||
return false;
|
||||
}
|
||||
assert(node._relayoutSubtreeRoot == node);
|
||||
return true;
|
||||
}
|
||||
void markNeedsLayout() {
|
||||
assert(!_debugDoingLayout);
|
||||
assert(!_debugDoingPaint);
|
||||
if (_needsLayout) {
|
||||
assert(debugAncestorsAlreadyMarkedNeedsLayout());
|
||||
return;
|
||||
}
|
||||
_needsLayout = true;
|
||||
assert(_relayoutSubtreeRoot != null);
|
||||
if (_relayoutSubtreeRoot != this) {
|
||||
assert(parent is RenderNode);
|
||||
parent.markNeedsLayout();
|
||||
} else {
|
||||
_nodesNeedingLayout.add(this);
|
||||
}
|
||||
}
|
||||
static void flushLayout() {
|
||||
_debugDoingLayout = true;
|
||||
List<RenderNode> dirtyNodes = _nodesNeedingLayout;
|
||||
_nodesNeedingLayout = new List<RenderNode>();
|
||||
dirtyNodes..sort((a, b) => a.depth - b.depth)..forEach((node) {
|
||||
if (node._needsLayout && node.attached)
|
||||
node._doLayout();
|
||||
});
|
||||
_debugDoingLayout = false;
|
||||
}
|
||||
void _doLayout() {
|
||||
try {
|
||||
assert(_relayoutSubtreeRoot == this);
|
||||
performLayout();
|
||||
} catch (e, stack) {
|
||||
print('Exception raised during layout of ${this}: ${e}');
|
||||
print(stack);
|
||||
return;
|
||||
}
|
||||
_needsLayout = false;
|
||||
}
|
||||
void layout(dynamic constraints, { bool parentUsesSize: false }) {
|
||||
RenderNode relayoutSubtreeRoot;
|
||||
if (!parentUsesSize || sizedByParent || parent is! RenderNode)
|
||||
relayoutSubtreeRoot = this;
|
||||
else
|
||||
relayoutSubtreeRoot = parent._relayoutSubtreeRoot;
|
||||
if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _relayoutSubtreeRoot)
|
||||
return;
|
||||
_constraints = constraints;
|
||||
_relayoutSubtreeRoot = relayoutSubtreeRoot;
|
||||
if (sizedByParent)
|
||||
performResize();
|
||||
performLayout();
|
||||
_needsLayout = false;
|
||||
markNeedsPaint();
|
||||
}
|
||||
bool get sizedByParent => false; // return true if the constraints are the only input to the sizing algorithm (in particular, child nodes have no impact)
|
||||
void performResize(); // set the local dimensions, using only the constraints (only called if sizedByParent is true)
|
||||
void performLayout();
|
||||
// Override this to perform relayout without your parent's
|
||||
// involvement.
|
||||
//
|
||||
// This is called during layout. If sizedByParent is true, then
|
||||
// performLayout() should not change your dimensions, only do that
|
||||
// in performResize(). If sizedByParent is false, then set both
|
||||
// your dimensions and do your children's layout here.
|
||||
//
|
||||
// When calling layout() on your children, pass in
|
||||
// "parentUsesSize: true" if your size or layout is dependent on
|
||||
// your child's size.
|
||||
|
||||
// when the parent has rotated (e.g. when the screen has been turned
|
||||
// 90 degrees), immediately prior to layout() being called for the
|
||||
// new dimensions, rotate() is called with the old and new angles.
|
||||
// The next time paint() is called, the coordinate space will have
|
||||
// been rotated N quarter-turns clockwise, where:
|
||||
// N = newAngle-oldAngle
|
||||
// ...but the rendering is expected to remain the same, pixel for
|
||||
// pixel, on the output device. Then, the layout() method or
|
||||
// equivalent will be invoked.
|
||||
|
||||
void rotate({
|
||||
int oldAngle, // 0..3
|
||||
int newAngle, // 0..3
|
||||
Duration time
|
||||
}) { }
|
||||
|
||||
|
||||
// PAINTING
|
||||
|
||||
static bool _debugDoingPaint = false;
|
||||
void markNeedsPaint() {
|
||||
assert(!_debugDoingPaint);
|
||||
// TODO(abarth): It's very redundant to call this for every node in the
|
||||
// render tree during layout. We should instead compute a summary bit and
|
||||
// call it once at the end of layout.
|
||||
sky.view.scheduleFrame();
|
||||
}
|
||||
void paint(RenderNodeDisplayList canvas) { }
|
||||
|
||||
|
||||
// HIT TESTING
|
||||
|
||||
void handlePointer(sky.PointerEvent event) {
|
||||
// override this if you have a client, to hand it to the client
|
||||
// override this if you want to do anything with the pointer event
|
||||
}
|
||||
|
||||
// RenderNode subclasses are expected to have a method like the
|
||||
// following (with the signature being whatever passes for coordinates
|
||||
// for this particular class):
|
||||
// bool hitTest(HitTestResult result, { sky.Point position }) {
|
||||
// // If (x,y) is not inside this node, then return false. (You
|
||||
// // can assume that the given coordinate is inside your
|
||||
// // dimensions. You only need to check this if you're an
|
||||
// // irregular shape, e.g. if you have a hole.)
|
||||
// // Otherwise:
|
||||
// // For each child that intersects x,y, in z-order starting from the top,
|
||||
// // call hitTest() for that child, passing it /result/, and the coordinates
|
||||
// // converted to the child's coordinate origin, and stop at the first child
|
||||
// // that returns true.
|
||||
// // Then, add yourself to /result/, and return true.
|
||||
// }
|
||||
// You must not add yourself to /result/ if you return false.
|
||||
|
||||
}
|
||||
|
||||
class HitTestResult {
|
||||
final List<RenderNode> path = new List<RenderNode>();
|
||||
|
||||
RenderNode get result => path.first;
|
||||
|
||||
void add(RenderNode node) {
|
||||
path.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD
|
||||
|
||||
abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> {
|
||||
ChildType _child;
|
||||
ChildType get child => _child;
|
||||
void set child (ChildType value) {
|
||||
if (_child != null)
|
||||
dropChild(_child);
|
||||
_child = value;
|
||||
if (_child != null)
|
||||
adoptChild(_child);
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GENERIC MIXIN FOR RENDER NODES WITH A LIST OF CHILDREN
|
||||
|
||||
abstract class ContainerParentDataMixin<ChildType extends RenderNode> {
|
||||
ChildType previousSibling;
|
||||
ChildType nextSibling;
|
||||
void detachSiblings() {
|
||||
if (previousSibling != null) {
|
||||
assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>);
|
||||
assert(previousSibling != this);
|
||||
assert(previousSibling.parentData.nextSibling == this);
|
||||
previousSibling.parentData.nextSibling = nextSibling;
|
||||
}
|
||||
if (nextSibling != null) {
|
||||
assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>);
|
||||
assert(nextSibling != this);
|
||||
assert(nextSibling.parentData.previousSibling == this);
|
||||
nextSibling.parentData.previousSibling = previousSibling;
|
||||
}
|
||||
previousSibling = null;
|
||||
nextSibling = null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentDataType extends ContainerParentDataMixin<ChildType>> implements RenderNode {
|
||||
// abstract class that has only InlineNode children
|
||||
|
||||
bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
while (child.parentData.previousSibling != null) {
|
||||
assert(child.parentData.previousSibling != child);
|
||||
child = child.parentData.previousSibling;
|
||||
assert(child.parentData is ParentDataType);
|
||||
}
|
||||
return child == equals;
|
||||
}
|
||||
bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
while (child.parentData.nextSibling != null) {
|
||||
assert(child.parentData.nextSibling != child);
|
||||
child = child.parentData.nextSibling;
|
||||
assert(child.parentData is ParentDataType);
|
||||
}
|
||||
return child == equals;
|
||||
}
|
||||
|
||||
ChildType _firstChild;
|
||||
ChildType _lastChild;
|
||||
void add(ChildType child, { ChildType before }) {
|
||||
assert(child != this);
|
||||
assert(before != this);
|
||||
assert(child != before);
|
||||
assert(child != _firstChild);
|
||||
assert(child != _lastChild);
|
||||
adoptChild(child);
|
||||
assert(child.parentData is ParentDataType);
|
||||
assert(child.parentData.nextSibling == null);
|
||||
assert(child.parentData.previousSibling == null);
|
||||
if (before == null) {
|
||||
// append at the end (_lastChild)
|
||||
child.parentData.previousSibling = _lastChild;
|
||||
if (_lastChild != null) {
|
||||
assert(_lastChild.parentData is ParentDataType);
|
||||
_lastChild.parentData.nextSibling = child;
|
||||
}
|
||||
_lastChild = child;
|
||||
if (_firstChild == null)
|
||||
_firstChild = child;
|
||||
} else {
|
||||
assert(_firstChild != null);
|
||||
assert(_lastChild != null);
|
||||
assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild));
|
||||
assert(_debugUltimateNextSiblingOf(before, equals: _lastChild));
|
||||
assert(before.parentData is ParentDataType);
|
||||
if (before.parentData.previousSibling == null) {
|
||||
// insert at the start (_firstChild); we'll end up with two or more children
|
||||
assert(before == _firstChild);
|
||||
child.parentData.nextSibling = before;
|
||||
before.parentData.previousSibling = child;
|
||||
_firstChild = child;
|
||||
} else {
|
||||
// insert in the middle; we'll end up with three or more children
|
||||
// set up links from child to siblings
|
||||
child.parentData.previousSibling = before.parentData.previousSibling;
|
||||
child.parentData.nextSibling = before;
|
||||
// set up links from siblings to child
|
||||
assert(child.parentData.previousSibling.parentData is ParentDataType);
|
||||
assert(child.parentData.nextSibling.parentData is ParentDataType);
|
||||
child.parentData.previousSibling.parentData.nextSibling = child;
|
||||
child.parentData.nextSibling.parentData.previousSibling = child;
|
||||
assert(before.parentData.previousSibling == child);
|
||||
}
|
||||
}
|
||||
markNeedsLayout();
|
||||
}
|
||||
void remove(ChildType child) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
|
||||
assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
|
||||
if (child.parentData.previousSibling == null) {
|
||||
assert(_firstChild == child);
|
||||
_firstChild = child.parentData.nextSibling;
|
||||
} else {
|
||||
assert(child.parentData.previousSibling.parentData is ParentDataType);
|
||||
child.parentData.previousSibling.parentData.nextSibling = child.parentData.nextSibling;
|
||||
}
|
||||
if (child.parentData.nextSibling == null) {
|
||||
assert(_lastChild == child);
|
||||
_lastChild = child.parentData.previousSibling;
|
||||
} else {
|
||||
assert(child.parentData.nextSibling.parentData is ParentDataType);
|
||||
child.parentData.nextSibling.parentData.previousSibling = child.parentData.previousSibling;
|
||||
}
|
||||
child.parentData.previousSibling = null;
|
||||
child.parentData.nextSibling = null;
|
||||
dropChild(child);
|
||||
markNeedsLayout();
|
||||
}
|
||||
void redepthChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
redepthChild(child);
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
void attachChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
child.attach();
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
void detachChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
child.detach();
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
ChildType get firstChild => _firstChild;
|
||||
ChildType get lastChild => _lastChild;
|
||||
ChildType childAfter(ChildType child) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
return child.parentData.nextSibling;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// GENERIC BOX RENDERING
|
||||
// Anything that has a concept of x, y, width, height is going to derive from this
|
||||
|
||||
class EdgeDims {
|
||||
// used for e.g. padding
|
||||
const EdgeDims(this.top, this.right, this.bottom, this.left);
|
||||
final double top;
|
||||
final double right;
|
||||
final double bottom;
|
||||
final double left;
|
||||
operator ==(EdgeDims other) => (top == other.top) ||
|
||||
(right == other.right) ||
|
||||
(bottom == other.bottom) ||
|
||||
(left == other.left);
|
||||
}
|
||||
|
||||
class BoxConstraints {
|
||||
const BoxConstraints({
|
||||
this.minWidth: 0.0,
|
||||
this.maxWidth: double.INFINITY,
|
||||
this.minHeight: 0.0,
|
||||
this.maxHeight: double.INFINITY});
|
||||
|
||||
BoxConstraints.tight(sky.Size size)
|
||||
: minWidth = size.width,
|
||||
maxWidth = size.width,
|
||||
minHeight = size.height,
|
||||
maxHeight = size.height;
|
||||
|
||||
BoxConstraints deflate(EdgeDims edges) {
|
||||
assert(edges != null);
|
||||
return new BoxConstraints(
|
||||
minWidth: minWidth,
|
||||
maxWidth: maxWidth - (edges.left + edges.right),
|
||||
minHeight: minHeight,
|
||||
maxHeight: maxHeight - (edges.top + edges.bottom)
|
||||
);
|
||||
}
|
||||
|
||||
final double minWidth;
|
||||
final double maxWidth;
|
||||
final double minHeight;
|
||||
final double maxHeight;
|
||||
|
||||
double constrainWidth(double width) {
|
||||
return clamp(min: minWidth, max: maxWidth, value: width);
|
||||
}
|
||||
|
||||
double constrainHeight(double height) {
|
||||
return clamp(min: minHeight, max: maxHeight, value: height);
|
||||
}
|
||||
|
||||
sky.Size constrain(sky.Size size) {
|
||||
return new sky.Size(constrainWidth(size.width), constrainHeight(size.height));
|
||||
}
|
||||
|
||||
bool get isInfinite => maxWidth >= double.INFINITY || maxHeight >= double.INFINITY;
|
||||
}
|
||||
|
||||
class BoxParentData extends ParentData {
|
||||
sky.Point position = new sky.Point(0.0, 0.0);
|
||||
}
|
||||
|
||||
abstract class RenderBox extends RenderNode {
|
||||
|
||||
void setParentData(RenderNode child) {
|
||||
if (child.parentData is! BoxParentData)
|
||||
child.parentData = new BoxParentData();
|
||||
}
|
||||
|
||||
// override this to report what dimensions you would have if you
|
||||
// were laid out with the given constraints this can walk the tree
|
||||
// if it must, but it should be as cheap as possible; just get the
|
||||
// dimensions and nothing else (e.g. don't calculate hypothetical
|
||||
// child positions if they're not needed to determine dimensions)
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
return constraints.constrain(new sky.Size(0.0, 0.0));
|
||||
}
|
||||
|
||||
BoxConstraints get constraints => super.constraints as BoxConstraints;
|
||||
void performResize() {
|
||||
// default behaviour for subclasses that have sizedByParent = true
|
||||
size = constraints.constrain(new sky.Size(0.0, 0.0));
|
||||
assert(size.height < double.INFINITY);
|
||||
assert(size.width < double.INFINITY);
|
||||
}
|
||||
void performLayout() {
|
||||
// descendants have to either override performLayout() to set both
|
||||
// width and height and lay out children, or, set sizedByParent to
|
||||
// true so that performResize()'s logic above does its thing.
|
||||
assert(sizedByParent);
|
||||
}
|
||||
|
||||
bool hitTest(HitTestResult result, { sky.Point position }) {
|
||||
hitTestChildren(result, position: position);
|
||||
result.add(this);
|
||||
return true;
|
||||
}
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) { }
|
||||
|
||||
sky.Size size = new sky.Size(0.0, 0.0);
|
||||
}
|
||||
|
||||
abstract class RenderProxyBox extends RenderBox with RenderNodeWithChildMixin<RenderBox> {
|
||||
RenderProxyBox(RenderBox child) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
if (child != null)
|
||||
return child.getIntrinsicDimensions(constraints);
|
||||
return super.getIntrinsicDimensions(constraints);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
if (child != null) {
|
||||
child.layout(constraints, parentUsesSize: true);
|
||||
size = child.size;
|
||||
} else {
|
||||
performResize();
|
||||
}
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
if (child != null)
|
||||
child.hitTest(result, position: position);
|
||||
else
|
||||
super.hitTestChildren(result, position: position);
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
if (child != null)
|
||||
child.paint(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderSizedBox extends RenderProxyBox {
|
||||
final sky.Size desiredSize;
|
||||
|
||||
RenderSizedBox({
|
||||
RenderBox child,
|
||||
this.desiredSize: const sky.Size.infinite()
|
||||
}) : super(child);
|
||||
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
return constraints.constrain(desiredSize);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
size = constraints.constrain(desiredSize);
|
||||
child.layout(new BoxConstraints.tight(size));
|
||||
}
|
||||
}
|
||||
|
||||
class RenderPadding extends RenderBox with RenderNodeWithChildMixin<RenderBox> {
|
||||
|
||||
RenderPadding(EdgeDims padding, RenderBox child) {
|
||||
assert(padding != null);
|
||||
this.padding = padding;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
EdgeDims _padding;
|
||||
EdgeDims get padding => _padding;
|
||||
void set padding (EdgeDims value) {
|
||||
assert(value != null);
|
||||
if (_padding != value) {
|
||||
_padding = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
assert(padding != null);
|
||||
constraints = constraints.deflate(padding);
|
||||
if (child == null)
|
||||
return super.getIntrinsicDimensions(constraints);
|
||||
return child.getIntrinsicDimensions(constraints);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
assert(padding != null);
|
||||
BoxConstraints innerConstraints = constraints.deflate(padding);
|
||||
if (child == null) {
|
||||
size = innerConstraints.constrain(
|
||||
new sky.Size(padding.left + padding.right, padding.top + padding.bottom));
|
||||
return;
|
||||
}
|
||||
child.layout(innerConstraints, parentUsesSize: true);
|
||||
assert(child.parentData is BoxParentData);
|
||||
child.parentData.position = new sky.Point(padding.left, padding.top);
|
||||
size = constraints.constrain(new sky.Size(padding.left + child.size.width + padding.right,
|
||||
padding.top + child.size.height + padding.bottom));
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
if (child != null)
|
||||
canvas.paintChild(child, child.parentData.position);
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
if (child != null) {
|
||||
assert(child.parentData is BoxParentData);
|
||||
sky.Rect childBounds = new sky.Rect.fromPointAndSize(child.parentData.position, child.size);
|
||||
if (childBounds.contains(position)) {
|
||||
child.hitTest(result, position: new sky.Point(position.x - child.parentData.position.x,
|
||||
position.y - child.parentData.position.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This must be immutable, because we won't notice when it changes
|
||||
class BoxDecoration {
|
||||
const BoxDecoration({
|
||||
this.backgroundColor
|
||||
});
|
||||
|
||||
final int backgroundColor;
|
||||
}
|
||||
|
||||
class RenderDecoratedBox extends RenderProxyBox {
|
||||
|
||||
RenderDecoratedBox({
|
||||
BoxDecoration decoration,
|
||||
RenderBox child
|
||||
}) : _decoration = decoration, super(child);
|
||||
|
||||
BoxDecoration _decoration;
|
||||
BoxDecoration get decoration => _decoration;
|
||||
void set decoration (BoxDecoration value) {
|
||||
if (value == _decoration)
|
||||
return;
|
||||
_decoration = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
assert(size.width != null);
|
||||
assert(size.height != null);
|
||||
|
||||
if (_decoration == null)
|
||||
return;
|
||||
|
||||
if (_decoration.backgroundColor != null) {
|
||||
sky.Paint paint = new sky.Paint()..color = _decoration.backgroundColor;
|
||||
canvas.drawRect(new sky.Rect.fromLTRB(0.0, 0.0, size.width, size.height), paint);
|
||||
}
|
||||
super.paint(canvas);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// RENDER VIEW LAYOUT MANAGER
|
||||
|
||||
class ViewConstraints {
|
||||
|
||||
const ViewConstraints({
|
||||
this.width: 0.0, this.height: 0.0, this.orientation: null
|
||||
});
|
||||
|
||||
final double width;
|
||||
final double height;
|
||||
final int orientation;
|
||||
|
||||
}
|
||||
|
||||
class RenderView extends RenderNode with RenderNodeWithChildMixin<RenderBox> {
|
||||
|
||||
RenderView({
|
||||
RenderBox child,
|
||||
this.timeForRotation: const Duration(microseconds: 83333)
|
||||
}) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
sky.Size _size = new sky.Size(0.0, 0.0);
|
||||
double get width => _size.width;
|
||||
double get height => _size.height;
|
||||
|
||||
int _orientation; // 0..3
|
||||
int get orientation => _orientation;
|
||||
Duration timeForRotation;
|
||||
|
||||
ViewConstraints get constraints => super.constraints as ViewConstraints;
|
||||
bool get sizedByParent => true;
|
||||
void performResize() {
|
||||
if (constraints.orientation != _orientation) {
|
||||
if (_orientation != null && child != null)
|
||||
child.rotate(oldAngle: _orientation, newAngle: constraints.orientation, time: timeForRotation);
|
||||
_orientation = constraints.orientation;
|
||||
}
|
||||
_size = new sky.Size(constraints.width, constraints.height);
|
||||
assert(_size.height < double.INFINITY);
|
||||
assert(_size.width < double.INFINITY);
|
||||
}
|
||||
void performLayout() {
|
||||
if (child != null) {
|
||||
child.layout(new BoxConstraints.tight(_size));
|
||||
assert(child.size.width == width);
|
||||
assert(child.size.height == height);
|
||||
}
|
||||
}
|
||||
|
||||
void rotate({ int oldAngle, int newAngle, Duration time }) {
|
||||
assert(false); // nobody tells the screen to rotate, the whole rotate() dance is started from our performResize()
|
||||
}
|
||||
|
||||
bool hitTest(HitTestResult result, { sky.Point position }) {
|
||||
if (child != null) {
|
||||
sky.Rect childBounds = new sky.Rect.fromSize(child.size);
|
||||
if (childBounds.contains(position))
|
||||
child.hitTest(result, position: position);
|
||||
}
|
||||
result.add(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
if (child != null)
|
||||
canvas.paintChild(child, new sky.Point(0.0, 0.0));
|
||||
}
|
||||
|
||||
void paintFrame() {
|
||||
RenderNode._debugDoingPaint = true;
|
||||
var canvas = new RenderNodeDisplayList(sky.view.width, sky.view.height);
|
||||
paint(canvas);
|
||||
sky.view.picture = canvas.endRecording();
|
||||
RenderNode._debugDoingPaint = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// DEFAULT BEHAVIORS FOR RENDERBOX CONTAINERS
|
||||
abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataType extends ContainerParentDataMixin<ChildType>> implements ContainerRenderNodeMixin<ChildType, ParentDataType> {
|
||||
|
||||
void defaultHitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
// the x, y parameters have the top left of the node's box as the origin
|
||||
ChildType child = lastChild;
|
||||
while (child != null) {
|
||||
assert(child.parentData is BoxParentData);
|
||||
sky.Rect childBounds = new sky.Rect.fromPointAndSize(child.parentData.position, child.size);
|
||||
if (childBounds.contains(position)) {
|
||||
if (child.hitTest(result, position: new sky.Point(position.x - child.parentData.position.x,
|
||||
position.y - child.parentData.position.y)))
|
||||
break;
|
||||
}
|
||||
child = child.parentData.previousSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void defaultPaint(RenderNodeDisplayList canvas) {
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
assert(child.parentData is BoxParentData);
|
||||
canvas.paintChild(child, child.parentData.position);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BLOCK LAYOUT MANAGER
|
||||
|
||||
class BlockParentData extends BoxParentData with ContainerParentDataMixin<RenderBox> { }
|
||||
|
||||
class RenderBlock extends RenderBox with ContainerRenderNodeMixin<RenderBox, BlockParentData>,
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> {
|
||||
// lays out RenderBox children in a vertical stack
|
||||
// uses the maximum width provided by the parent
|
||||
// sizes itself to the height of its child stack
|
||||
|
||||
void setParentData(RenderBox child) {
|
||||
if (child.parentData is! BlockParentData)
|
||||
child.parentData = new BlockParentData();
|
||||
}
|
||||
|
||||
// override this to report what dimensions you would have if you
|
||||
// were laid out with the given constraints this can walk the tree
|
||||
// if it must, but it should be as cheap as possible; just get the
|
||||
// dimensions and nothing else (e.g. don't calculate hypothetical
|
||||
// child positions if they're not needed to determine dimensions)
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
double height = 0.0;
|
||||
double width = constraints.constrainWidth(constraints.maxWidth);
|
||||
assert(width < double.INFINITY);
|
||||
RenderBox child = firstChild;
|
||||
BoxConstraints innerConstraints = new BoxConstraints(minWidth: width,
|
||||
maxWidth: width);
|
||||
while (child != null) {
|
||||
height += child.getIntrinsicDimensions(innerConstraints).height;
|
||||
assert(child.parentData is BlockParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
|
||||
return new sky.Size(width, constraints.constrainHeight(height));
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
assert(constraints is BoxConstraints);
|
||||
double width = constraints.constrainWidth(constraints.maxWidth);
|
||||
double y = 0.0;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
child.layout(new BoxConstraints(minWidth: width, maxWidth: width), parentUsesSize: true);
|
||||
assert(child.parentData is BlockParentData);
|
||||
child.parentData.position = new sky.Point(0.0, y);
|
||||
y += child.size.height;
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
size = new sky.Size(width, constraints.constrainHeight(y));
|
||||
assert(size.width < double.INFINITY);
|
||||
assert(size.height < double.INFINITY);
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
defaultHitTestChildren(result, position: position);
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
defaultPaint(canvas);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// FLEXBOX LAYOUT MANAGER
|
||||
|
||||
class FlexBoxParentData extends BoxParentData with ContainerParentDataMixin<RenderBox> {
|
||||
int flex;
|
||||
void merge(FlexBoxParentData other) {
|
||||
if (other.flex != null)
|
||||
flex = other.flex;
|
||||
super.merge(other);
|
||||
}
|
||||
}
|
||||
|
||||
enum FlexDirection { Horizontal, Vertical }
|
||||
|
||||
class RenderFlex extends RenderBox with ContainerRenderNodeMixin<RenderBox, FlexBoxParentData>,
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> {
|
||||
// lays out RenderBox children using flexible layout
|
||||
|
||||
RenderFlex({
|
||||
FlexDirection direction: FlexDirection.Horizontal
|
||||
}) : _direction = direction;
|
||||
|
||||
FlexDirection _direction;
|
||||
FlexDirection get direction => _direction;
|
||||
void set direction (FlexDirection value) {
|
||||
if (_direction != value) {
|
||||
_direction = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void setParentData(RenderBox child) {
|
||||
if (child.parentData is! FlexBoxParentData)
|
||||
child.parentData = new FlexBoxParentData();
|
||||
}
|
||||
|
||||
bool get sizedByParent => true;
|
||||
void performResize() {
|
||||
size = _constraints.constrain(new sky.Size(_constraints.maxWidth, _constraints.maxHeight));
|
||||
assert(size.height < double.INFINITY);
|
||||
assert(size.width < double.INFINITY);
|
||||
}
|
||||
|
||||
int _getFlex(RenderBox child) {
|
||||
assert(child.parentData is FlexBoxParentData);
|
||||
return child.parentData.flex != null ? child.parentData.flex : 0;
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
// Based on http://www.w3.org/TR/css-flexbox-1/ Section 9.7 Resolving Flexible Lengths
|
||||
// Steps 1-3. Determine used flex factor, size inflexible items, calculate free space
|
||||
int totalFlex = 0;
|
||||
assert(constraints != null);
|
||||
double freeSpace = (_direction == FlexDirection.Horizontal) ? constraints.maxWidth : constraints.maxHeight;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
int flex = _getFlex(child);
|
||||
if (flex > 0) {
|
||||
totalFlex += child.parentData.flex;
|
||||
} else {
|
||||
BoxConstraints innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight,
|
||||
maxWidth: constraints.maxWidth);
|
||||
child.layout(innerConstraints, parentUsesSize: true);
|
||||
freeSpace -= (_direction == FlexDirection.Horizontal) ? child.size.width : child.size.height;
|
||||
}
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
|
||||
// Steps 4-5. Distribute remaining space to flexible children.
|
||||
double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
|
||||
double usedSpace = 0.0;
|
||||
child = firstChild;
|
||||
while (child != null) {
|
||||
int flex = _getFlex(child);
|
||||
if (flex > 0) {
|
||||
double spaceForChild = spacePerFlex * flex;
|
||||
BoxConstraints innerConstraints;
|
||||
switch (_direction) {
|
||||
case FlexDirection.Horizontal:
|
||||
innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight,
|
||||
minWidth: spaceForChild,
|
||||
maxWidth: spaceForChild);
|
||||
break;
|
||||
case FlexDirection.Vertical:
|
||||
innerConstraints = new BoxConstraints(minHeight: spaceForChild,
|
||||
maxHeight: spaceForChild,
|
||||
maxWidth: constraints.maxWidth);
|
||||
break;
|
||||
}
|
||||
child.layout(innerConstraints, parentUsesSize: true);
|
||||
}
|
||||
|
||||
// For now, center the flex items in the cross direction
|
||||
switch (_direction) {
|
||||
case FlexDirection.Horizontal:
|
||||
child.parentData.position = new sky.Point(usedSpace, size.height / 2.0 - child.size.height / 2.0);
|
||||
usedSpace += child.size.width;
|
||||
break;
|
||||
case FlexDirection.Vertical:
|
||||
child.parentData.position = new sky.Point(size.width / 2.0 - child.size.width / 2.0, usedSpace);
|
||||
usedSpace += child.size.height;
|
||||
break;
|
||||
}
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
defaultHitTestChildren(result, position: position);
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
defaultPaint(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderInline extends RenderNode {
|
||||
String data;
|
||||
|
||||
RenderInline(this.data);
|
||||
}
|
||||
|
||||
class RenderParagraph extends RenderBox {
|
||||
|
||||
RenderParagraph({
|
||||
String text,
|
||||
int color
|
||||
}) : _color = color {
|
||||
_layoutRoot.rootElement = _document.createElement('p');
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
final sky.Document _document = new sky.Document();
|
||||
final sky.LayoutRoot _layoutRoot = new sky.LayoutRoot();
|
||||
|
||||
String get text => (_layoutRoot.rootElement.firstChild as sky.Text).data;
|
||||
void set text (String value) {
|
||||
_layoutRoot.rootElement.setChild(_document.createText(value));
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
int _color = 0xFF000000;
|
||||
int get color => _color;
|
||||
void set color (int value) {
|
||||
if (_color != value) {
|
||||
_color = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
assert(false);
|
||||
return null;
|
||||
// we don't currently support this for RenderParagraph
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
_layoutRoot.maxWidth = constraints.maxWidth;
|
||||
_layoutRoot.minWidth = constraints.minWidth;
|
||||
_layoutRoot.minHeight = constraints.minHeight;
|
||||
_layoutRoot.maxHeight = constraints.maxHeight;
|
||||
_layoutRoot.layout();
|
||||
size = constraints.constrain(new sky.Size(_layoutRoot.rootElement.width, _layoutRoot.rootElement.height));
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
// _layoutRoot.rootElement.style['color'] = 'rgba(' + ...color... + ')';
|
||||
_layoutRoot.paint(canvas);
|
||||
}
|
||||
|
||||
// we should probably expose a way to do precise (inter-glpyh) hit testing
|
||||
|
||||
}
|
||||
69
sdk/lib/framework/rendering/render_block.dart
Normal file
69
sdk/lib/framework/rendering/render_block.dart
Normal file
@ -0,0 +1,69 @@
|
||||
// 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 'render_box.dart';
|
||||
import 'render_node.dart';
|
||||
|
||||
class BlockParentData extends BoxParentData with ContainerParentDataMixin<RenderBox> { }
|
||||
|
||||
class RenderBlock extends RenderBox with ContainerRenderNodeMixin<RenderBox, BlockParentData>,
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> {
|
||||
// lays out RenderBox children in a vertical stack
|
||||
// uses the maximum width provided by the parent
|
||||
// sizes itself to the height of its child stack
|
||||
|
||||
void setParentData(RenderBox child) {
|
||||
if (child.parentData is! BlockParentData)
|
||||
child.parentData = new BlockParentData();
|
||||
}
|
||||
|
||||
// override this to report what dimensions you would have if you
|
||||
// were laid out with the given constraints this can walk the tree
|
||||
// if it must, but it should be as cheap as possible; just get the
|
||||
// dimensions and nothing else (e.g. don't calculate hypothetical
|
||||
// child positions if they're not needed to determine dimensions)
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
double height = 0.0;
|
||||
double width = constraints.constrainWidth(constraints.maxWidth);
|
||||
assert(width < double.INFINITY);
|
||||
RenderBox child = firstChild;
|
||||
BoxConstraints innerConstraints = new BoxConstraints(minWidth: width,
|
||||
maxWidth: width);
|
||||
while (child != null) {
|
||||
height += child.getIntrinsicDimensions(innerConstraints).height;
|
||||
assert(child.parentData is BlockParentData);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
|
||||
return new sky.Size(width, constraints.constrainHeight(height));
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
assert(constraints is BoxConstraints);
|
||||
double width = constraints.constrainWidth(constraints.maxWidth);
|
||||
double y = 0.0;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
child.layout(new BoxConstraints(minWidth: width, maxWidth: width), parentUsesSize: true);
|
||||
assert(child.parentData is BlockParentData);
|
||||
child.parentData.position = new sky.Point(0.0, y);
|
||||
y += child.size.height;
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
size = new sky.Size(width, constraints.constrainHeight(y));
|
||||
assert(size.width < double.INFINITY);
|
||||
assert(size.height < double.INFINITY);
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
defaultHitTestChildren(result, position: position);
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
defaultPaint(canvas);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
369
sdk/lib/framework/rendering/render_box.dart
Normal file
369
sdk/lib/framework/rendering/render_box.dart
Normal file
@ -0,0 +1,369 @@
|
||||
// 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 'render_node.dart';
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
// GENERIC BOX RENDERING
|
||||
// Anything that has a concept of x, y, width, height is going to derive from this
|
||||
|
||||
class EdgeDims {
|
||||
// used for e.g. padding
|
||||
const EdgeDims(this.top, this.right, this.bottom, this.left);
|
||||
final double top;
|
||||
final double right;
|
||||
final double bottom;
|
||||
final double left;
|
||||
operator ==(EdgeDims other) => (top == other.top) ||
|
||||
(right == other.right) ||
|
||||
(bottom == other.bottom) ||
|
||||
(left == other.left);
|
||||
}
|
||||
|
||||
class BoxConstraints {
|
||||
const BoxConstraints({
|
||||
this.minWidth: 0.0,
|
||||
this.maxWidth: double.INFINITY,
|
||||
this.minHeight: 0.0,
|
||||
this.maxHeight: double.INFINITY});
|
||||
|
||||
BoxConstraints.tight(sky.Size size)
|
||||
: minWidth = size.width,
|
||||
maxWidth = size.width,
|
||||
minHeight = size.height,
|
||||
maxHeight = size.height;
|
||||
|
||||
BoxConstraints deflate(EdgeDims edges) {
|
||||
assert(edges != null);
|
||||
return new BoxConstraints(
|
||||
minWidth: minWidth,
|
||||
maxWidth: maxWidth - (edges.left + edges.right),
|
||||
minHeight: minHeight,
|
||||
maxHeight: maxHeight - (edges.top + edges.bottom)
|
||||
);
|
||||
}
|
||||
|
||||
final double minWidth;
|
||||
final double maxWidth;
|
||||
final double minHeight;
|
||||
final double maxHeight;
|
||||
|
||||
double constrainWidth(double width) {
|
||||
return clamp(min: minWidth, max: maxWidth, value: width);
|
||||
}
|
||||
|
||||
double constrainHeight(double height) {
|
||||
return clamp(min: minHeight, max: maxHeight, value: height);
|
||||
}
|
||||
|
||||
sky.Size constrain(sky.Size size) {
|
||||
return new sky.Size(constrainWidth(size.width), constrainHeight(size.height));
|
||||
}
|
||||
|
||||
bool get isInfinite => maxWidth >= double.INFINITY || maxHeight >= double.INFINITY;
|
||||
}
|
||||
|
||||
class BoxParentData extends ParentData {
|
||||
sky.Point position = new sky.Point(0.0, 0.0);
|
||||
}
|
||||
|
||||
abstract class RenderBox extends RenderNode {
|
||||
|
||||
void setParentData(RenderNode child) {
|
||||
if (child.parentData is! BoxParentData)
|
||||
child.parentData = new BoxParentData();
|
||||
}
|
||||
|
||||
// override this to report what dimensions you would have if you
|
||||
// were laid out with the given constraints this can walk the tree
|
||||
// if it must, but it should be as cheap as possible; just get the
|
||||
// dimensions and nothing else (e.g. don't calculate hypothetical
|
||||
// child positions if they're not needed to determine dimensions)
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
return constraints.constrain(new sky.Size(0.0, 0.0));
|
||||
}
|
||||
|
||||
BoxConstraints get constraints => super.constraints as BoxConstraints;
|
||||
void performResize() {
|
||||
// default behaviour for subclasses that have sizedByParent = true
|
||||
size = constraints.constrain(new sky.Size(0.0, 0.0));
|
||||
assert(size.height < double.INFINITY);
|
||||
assert(size.width < double.INFINITY);
|
||||
}
|
||||
void performLayout() {
|
||||
// descendants have to either override performLayout() to set both
|
||||
// width and height and lay out children, or, set sizedByParent to
|
||||
// true so that performResize()'s logic above does its thing.
|
||||
assert(sizedByParent);
|
||||
}
|
||||
|
||||
bool hitTest(HitTestResult result, { sky.Point position }) {
|
||||
hitTestChildren(result, position: position);
|
||||
result.add(this);
|
||||
return true;
|
||||
}
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) { }
|
||||
|
||||
sky.Size size = new sky.Size(0.0, 0.0);
|
||||
}
|
||||
|
||||
abstract class RenderProxyBox extends RenderBox with RenderNodeWithChildMixin<RenderBox> {
|
||||
RenderProxyBox(RenderBox child) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
if (child != null)
|
||||
return child.getIntrinsicDimensions(constraints);
|
||||
return super.getIntrinsicDimensions(constraints);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
if (child != null) {
|
||||
child.layout(constraints, parentUsesSize: true);
|
||||
size = child.size;
|
||||
} else {
|
||||
performResize();
|
||||
}
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
if (child != null)
|
||||
child.hitTest(result, position: position);
|
||||
else
|
||||
super.hitTestChildren(result, position: position);
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
if (child != null)
|
||||
child.paint(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderSizedBox extends RenderProxyBox {
|
||||
final sky.Size desiredSize;
|
||||
|
||||
RenderSizedBox({
|
||||
RenderBox child,
|
||||
this.desiredSize: const sky.Size.infinite()
|
||||
}) : super(child);
|
||||
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
return constraints.constrain(desiredSize);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
size = constraints.constrain(desiredSize);
|
||||
child.layout(new BoxConstraints.tight(size));
|
||||
}
|
||||
}
|
||||
|
||||
class RenderPadding extends RenderBox with RenderNodeWithChildMixin<RenderBox> {
|
||||
|
||||
RenderPadding(EdgeDims padding, RenderBox child) {
|
||||
assert(padding != null);
|
||||
this.padding = padding;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
EdgeDims _padding;
|
||||
EdgeDims get padding => _padding;
|
||||
void set padding (EdgeDims value) {
|
||||
assert(value != null);
|
||||
if (_padding != value) {
|
||||
_padding = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
assert(padding != null);
|
||||
constraints = constraints.deflate(padding);
|
||||
if (child == null)
|
||||
return super.getIntrinsicDimensions(constraints);
|
||||
return child.getIntrinsicDimensions(constraints);
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
assert(padding != null);
|
||||
BoxConstraints innerConstraints = constraints.deflate(padding);
|
||||
if (child == null) {
|
||||
size = innerConstraints.constrain(
|
||||
new sky.Size(padding.left + padding.right, padding.top + padding.bottom));
|
||||
return;
|
||||
}
|
||||
child.layout(innerConstraints, parentUsesSize: true);
|
||||
assert(child.parentData is BoxParentData);
|
||||
child.parentData.position = new sky.Point(padding.left, padding.top);
|
||||
size = constraints.constrain(new sky.Size(padding.left + child.size.width + padding.right,
|
||||
padding.top + child.size.height + padding.bottom));
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
if (child != null)
|
||||
canvas.paintChild(child, child.parentData.position);
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
if (child != null) {
|
||||
assert(child.parentData is BoxParentData);
|
||||
sky.Rect childBounds = new sky.Rect.fromPointAndSize(child.parentData.position, child.size);
|
||||
if (childBounds.contains(position)) {
|
||||
child.hitTest(result, position: new sky.Point(position.x - child.parentData.position.x,
|
||||
position.y - child.parentData.position.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This must be immutable, because we won't notice when it changes
|
||||
class BoxDecoration {
|
||||
const BoxDecoration({
|
||||
this.backgroundColor
|
||||
});
|
||||
|
||||
final int backgroundColor;
|
||||
}
|
||||
|
||||
class RenderDecoratedBox extends RenderProxyBox {
|
||||
|
||||
RenderDecoratedBox({
|
||||
BoxDecoration decoration,
|
||||
RenderBox child
|
||||
}) : _decoration = decoration, super(child);
|
||||
|
||||
BoxDecoration _decoration;
|
||||
BoxDecoration get decoration => _decoration;
|
||||
void set decoration (BoxDecoration value) {
|
||||
if (value == _decoration)
|
||||
return;
|
||||
_decoration = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
assert(size.width != null);
|
||||
assert(size.height != null);
|
||||
|
||||
if (_decoration == null)
|
||||
return;
|
||||
|
||||
if (_decoration.backgroundColor != null) {
|
||||
sky.Paint paint = new sky.Paint()..color = _decoration.backgroundColor;
|
||||
canvas.drawRect(new sky.Rect.fromLTRB(0.0, 0.0, size.width, size.height), paint);
|
||||
}
|
||||
super.paint(canvas);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// RENDER VIEW LAYOUT MANAGER
|
||||
|
||||
class ViewConstraints {
|
||||
|
||||
const ViewConstraints({
|
||||
this.width: 0.0, this.height: 0.0, this.orientation: null
|
||||
});
|
||||
|
||||
final double width;
|
||||
final double height;
|
||||
final int orientation;
|
||||
|
||||
}
|
||||
|
||||
class RenderView extends RenderNode with RenderNodeWithChildMixin<RenderBox> {
|
||||
|
||||
RenderView({
|
||||
RenderBox child,
|
||||
this.timeForRotation: const Duration(microseconds: 83333)
|
||||
}) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
sky.Size _size = new sky.Size(0.0, 0.0);
|
||||
double get width => _size.width;
|
||||
double get height => _size.height;
|
||||
|
||||
int _orientation; // 0..3
|
||||
int get orientation => _orientation;
|
||||
Duration timeForRotation;
|
||||
|
||||
ViewConstraints get constraints => super.constraints as ViewConstraints;
|
||||
bool get sizedByParent => true;
|
||||
void performResize() {
|
||||
if (constraints.orientation != _orientation) {
|
||||
if (_orientation != null && child != null)
|
||||
child.rotate(oldAngle: _orientation, newAngle: constraints.orientation, time: timeForRotation);
|
||||
_orientation = constraints.orientation;
|
||||
}
|
||||
_size = new sky.Size(constraints.width, constraints.height);
|
||||
assert(_size.height < double.INFINITY);
|
||||
assert(_size.width < double.INFINITY);
|
||||
}
|
||||
void performLayout() {
|
||||
if (child != null) {
|
||||
child.layout(new BoxConstraints.tight(_size));
|
||||
assert(child.size.width == width);
|
||||
assert(child.size.height == height);
|
||||
}
|
||||
}
|
||||
|
||||
void rotate({ int oldAngle, int newAngle, Duration time }) {
|
||||
assert(false); // nobody tells the screen to rotate, the whole rotate() dance is started from our performResize()
|
||||
}
|
||||
|
||||
bool hitTest(HitTestResult result, { sky.Point position }) {
|
||||
if (child != null) {
|
||||
sky.Rect childBounds = new sky.Rect.fromSize(child.size);
|
||||
if (childBounds.contains(position))
|
||||
child.hitTest(result, position: position);
|
||||
}
|
||||
result.add(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
if (child != null)
|
||||
canvas.paintChild(child, new sky.Point(0.0, 0.0));
|
||||
}
|
||||
|
||||
void paintFrame() {
|
||||
RenderNode.debugDoingPaint = true;
|
||||
var canvas = new RenderNodeDisplayList(sky.view.width, sky.view.height);
|
||||
paint(canvas);
|
||||
sky.view.picture = canvas.endRecording();
|
||||
RenderNode.debugDoingPaint = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// DEFAULT BEHAVIORS FOR RENDERBOX CONTAINERS
|
||||
abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataType extends ContainerParentDataMixin<ChildType>> implements ContainerRenderNodeMixin<ChildType, ParentDataType> {
|
||||
|
||||
void defaultHitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
// the x, y parameters have the top left of the node's box as the origin
|
||||
ChildType child = lastChild;
|
||||
while (child != null) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
sky.Rect childBounds = new sky.Rect.fromPointAndSize(child.parentData.position, child.size);
|
||||
if (childBounds.contains(position)) {
|
||||
if (child.hitTest(result, position: new sky.Point(position.x - child.parentData.position.x,
|
||||
position.y - child.parentData.position.y)))
|
||||
break;
|
||||
}
|
||||
child = child.parentData.previousSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void defaultPaint(RenderNodeDisplayList canvas) {
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
canvas.paintChild(child, child.parentData.position);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
sdk/lib/framework/rendering/render_flex.dart
Normal file
121
sdk/lib/framework/rendering/render_flex.dart
Normal file
@ -0,0 +1,121 @@
|
||||
// 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 'render_node.dart';
|
||||
import 'render_box.dart';
|
||||
import 'render_node.dart';
|
||||
|
||||
class FlexBoxParentData extends BoxParentData with ContainerParentDataMixin<RenderBox> {
|
||||
int flex;
|
||||
void merge(FlexBoxParentData other) {
|
||||
if (other.flex != null)
|
||||
flex = other.flex;
|
||||
super.merge(other);
|
||||
}
|
||||
}
|
||||
|
||||
enum FlexDirection { Horizontal, Vertical }
|
||||
|
||||
class RenderFlex extends RenderBox with ContainerRenderNodeMixin<RenderBox, FlexBoxParentData>,
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, FlexBoxParentData> {
|
||||
// lays out RenderBox children using flexible layout
|
||||
|
||||
RenderFlex({
|
||||
FlexDirection direction: FlexDirection.Horizontal
|
||||
}) : _direction = direction;
|
||||
|
||||
FlexDirection _direction;
|
||||
FlexDirection get direction => _direction;
|
||||
void set direction (FlexDirection value) {
|
||||
if (_direction != value) {
|
||||
_direction = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void setParentData(RenderBox child) {
|
||||
if (child.parentData is! FlexBoxParentData)
|
||||
child.parentData = new FlexBoxParentData();
|
||||
}
|
||||
|
||||
bool get sizedByParent => true;
|
||||
void performResize() {
|
||||
size = constraints.constrain(new sky.Size(constraints.maxWidth, constraints.maxHeight));
|
||||
assert(size.height < double.INFINITY);
|
||||
assert(size.width < double.INFINITY);
|
||||
}
|
||||
|
||||
int _getFlex(RenderBox child) {
|
||||
assert(child.parentData is FlexBoxParentData);
|
||||
return child.parentData.flex != null ? child.parentData.flex : 0;
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
// Based on http://www.w3.org/TR/css-flexbox-1/ Section 9.7 Resolving Flexible Lengths
|
||||
// Steps 1-3. Determine used flex factor, size inflexible items, calculate free space
|
||||
int totalFlex = 0;
|
||||
assert(constraints != null);
|
||||
double freeSpace = (_direction == FlexDirection.Horizontal) ? constraints.maxWidth : constraints.maxHeight;
|
||||
RenderBox child = firstChild;
|
||||
while (child != null) {
|
||||
int flex = _getFlex(child);
|
||||
if (flex > 0) {
|
||||
totalFlex += child.parentData.flex;
|
||||
} else {
|
||||
BoxConstraints innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight,
|
||||
maxWidth: constraints.maxWidth);
|
||||
child.layout(innerConstraints, parentUsesSize: true);
|
||||
freeSpace -= (_direction == FlexDirection.Horizontal) ? child.size.width : child.size.height;
|
||||
}
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
|
||||
// Steps 4-5. Distribute remaining space to flexible children.
|
||||
double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0;
|
||||
double usedSpace = 0.0;
|
||||
child = firstChild;
|
||||
while (child != null) {
|
||||
int flex = _getFlex(child);
|
||||
if (flex > 0) {
|
||||
double spaceForChild = spacePerFlex * flex;
|
||||
BoxConstraints innerConstraints;
|
||||
switch (_direction) {
|
||||
case FlexDirection.Horizontal:
|
||||
innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight,
|
||||
minWidth: spaceForChild,
|
||||
maxWidth: spaceForChild);
|
||||
break;
|
||||
case FlexDirection.Vertical:
|
||||
innerConstraints = new BoxConstraints(minHeight: spaceForChild,
|
||||
maxHeight: spaceForChild,
|
||||
maxWidth: constraints.maxWidth);
|
||||
break;
|
||||
}
|
||||
child.layout(innerConstraints, parentUsesSize: true);
|
||||
}
|
||||
|
||||
// For now, center the flex items in the cross direction
|
||||
switch (_direction) {
|
||||
case FlexDirection.Horizontal:
|
||||
child.parentData.position = new sky.Point(usedSpace, size.height / 2.0 - child.size.height / 2.0);
|
||||
usedSpace += child.size.width;
|
||||
break;
|
||||
case FlexDirection.Vertical:
|
||||
child.parentData.position = new sky.Point(size.width / 2.0 - child.size.width / 2.0, usedSpace);
|
||||
usedSpace += child.size.height;
|
||||
break;
|
||||
}
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void hitTestChildren(HitTestResult result, { sky.Point position }) {
|
||||
defaultHitTestChildren(result, position: position);
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
defaultPaint(canvas);
|
||||
}
|
||||
}
|
||||
390
sdk/lib/framework/rendering/render_node.dart
Normal file
390
sdk/lib/framework/rendering/render_node.dart
Normal file
@ -0,0 +1,390 @@
|
||||
// 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 '../node.dart';
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
class ParentData {
|
||||
void detach() {
|
||||
detachSiblings();
|
||||
}
|
||||
void detachSiblings() { } // workaround for lack of inter-class mixins in Dart
|
||||
void merge(ParentData other) {
|
||||
// override this in subclasses to merge in data from other into this
|
||||
assert(other.runtimeType == this.runtimeType);
|
||||
}
|
||||
}
|
||||
|
||||
const kLayoutDirections = 4;
|
||||
|
||||
double clamp({double min: 0.0, double value: 0.0, double max: double.INFINITY}) {
|
||||
assert(min != null);
|
||||
assert(value != null);
|
||||
assert(max != null);
|
||||
|
||||
if (value > max)
|
||||
value = max;
|
||||
if (value < min)
|
||||
value = min;
|
||||
return value;
|
||||
}
|
||||
|
||||
class RenderNodeDisplayList extends sky.PictureRecorder {
|
||||
RenderNodeDisplayList(double width, double height) : super(width, height);
|
||||
void paintChild(RenderNode child, sky.Point position) {
|
||||
save();
|
||||
translate(position.x, position.y);
|
||||
child.paint(this);
|
||||
restore();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class RenderNode extends AbstractNode {
|
||||
|
||||
// LAYOUT
|
||||
|
||||
// parentData is only for use by the RenderNode that actually lays this
|
||||
// node out, and any other nodes who happen to know exactly what
|
||||
// kind of node that is.
|
||||
ParentData parentData;
|
||||
void setParentData(RenderNode child) {
|
||||
// override this to setup .parentData correctly for your class
|
||||
if (child.parentData is! ParentData)
|
||||
child.parentData = new ParentData();
|
||||
}
|
||||
|
||||
void adoptChild(RenderNode child) { // only for use by subclasses
|
||||
// call this whenever you decide a node is a child
|
||||
assert(child != null);
|
||||
setParentData(child);
|
||||
super.adoptChild(child);
|
||||
}
|
||||
void dropChild(RenderNode child) { // only for use by subclasses
|
||||
assert(child != null);
|
||||
assert(child.parentData != null);
|
||||
child.parentData.detach();
|
||||
super.dropChild(child);
|
||||
}
|
||||
|
||||
static List<RenderNode> _nodesNeedingLayout = new List<RenderNode>();
|
||||
static bool _debugDoingLayout = false;
|
||||
bool _needsLayout = true;
|
||||
bool get needsLayout => _needsLayout;
|
||||
RenderNode _relayoutSubtreeRoot;
|
||||
dynamic _constraints;
|
||||
dynamic get constraints => _constraints;
|
||||
bool debugAncestorsAlreadyMarkedNeedsLayout() {
|
||||
if (_relayoutSubtreeRoot == null)
|
||||
return true; // we haven't yet done layout even once, so there's nothing for us to do
|
||||
RenderNode node = this;
|
||||
while (node != _relayoutSubtreeRoot) {
|
||||
assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot);
|
||||
assert(node.parent != null);
|
||||
node = node.parent as RenderNode;
|
||||
if (!node._needsLayout)
|
||||
return false;
|
||||
}
|
||||
assert(node._relayoutSubtreeRoot == node);
|
||||
return true;
|
||||
}
|
||||
void markNeedsLayout() {
|
||||
assert(!_debugDoingLayout);
|
||||
assert(!debugDoingPaint);
|
||||
if (_needsLayout) {
|
||||
assert(debugAncestorsAlreadyMarkedNeedsLayout());
|
||||
return;
|
||||
}
|
||||
_needsLayout = true;
|
||||
assert(_relayoutSubtreeRoot != null);
|
||||
if (_relayoutSubtreeRoot != this) {
|
||||
assert(parent is RenderNode);
|
||||
parent.markNeedsLayout();
|
||||
} else {
|
||||
_nodesNeedingLayout.add(this);
|
||||
}
|
||||
}
|
||||
static void flushLayout() {
|
||||
_debugDoingLayout = true;
|
||||
List<RenderNode> dirtyNodes = _nodesNeedingLayout;
|
||||
_nodesNeedingLayout = new List<RenderNode>();
|
||||
dirtyNodes..sort((a, b) => a.depth - b.depth)..forEach((node) {
|
||||
if (node._needsLayout && node.attached)
|
||||
node._doLayout();
|
||||
});
|
||||
_debugDoingLayout = false;
|
||||
}
|
||||
void _doLayout() {
|
||||
try {
|
||||
assert(_relayoutSubtreeRoot == this);
|
||||
performLayout();
|
||||
} catch (e, stack) {
|
||||
print('Exception raised during layout of ${this}: ${e}');
|
||||
print(stack);
|
||||
return;
|
||||
}
|
||||
_needsLayout = false;
|
||||
}
|
||||
void layout(dynamic constraints, { bool parentUsesSize: false }) {
|
||||
RenderNode relayoutSubtreeRoot;
|
||||
if (!parentUsesSize || sizedByParent || parent is! RenderNode)
|
||||
relayoutSubtreeRoot = this;
|
||||
else
|
||||
relayoutSubtreeRoot = parent._relayoutSubtreeRoot;
|
||||
if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _relayoutSubtreeRoot)
|
||||
return;
|
||||
_constraints = constraints;
|
||||
_relayoutSubtreeRoot = relayoutSubtreeRoot;
|
||||
if (sizedByParent)
|
||||
performResize();
|
||||
performLayout();
|
||||
_needsLayout = false;
|
||||
markNeedsPaint();
|
||||
}
|
||||
bool get sizedByParent => false; // return true if the constraints are the only input to the sizing algorithm (in particular, child nodes have no impact)
|
||||
void performResize(); // set the local dimensions, using only the constraints (only called if sizedByParent is true)
|
||||
void performLayout();
|
||||
// Override this to perform relayout without your parent's
|
||||
// involvement.
|
||||
//
|
||||
// This is called during layout. If sizedByParent is true, then
|
||||
// performLayout() should not change your dimensions, only do that
|
||||
// in performResize(). If sizedByParent is false, then set both
|
||||
// your dimensions and do your children's layout here.
|
||||
//
|
||||
// When calling layout() on your children, pass in
|
||||
// "parentUsesSize: true" if your size or layout is dependent on
|
||||
// your child's size.
|
||||
|
||||
// when the parent has rotated (e.g. when the screen has been turned
|
||||
// 90 degrees), immediately prior to layout() being called for the
|
||||
// new dimensions, rotate() is called with the old and new angles.
|
||||
// The next time paint() is called, the coordinate space will have
|
||||
// been rotated N quarter-turns clockwise, where:
|
||||
// N = newAngle-oldAngle
|
||||
// ...but the rendering is expected to remain the same, pixel for
|
||||
// pixel, on the output device. Then, the layout() method or
|
||||
// equivalent will be invoked.
|
||||
|
||||
void rotate({
|
||||
int oldAngle, // 0..3
|
||||
int newAngle, // 0..3
|
||||
Duration time
|
||||
}) { }
|
||||
|
||||
|
||||
// PAINTING
|
||||
|
||||
static bool debugDoingPaint = false;
|
||||
void markNeedsPaint() {
|
||||
assert(!debugDoingPaint);
|
||||
// TODO(abarth): It's very redundant to call this for every node in the
|
||||
// render tree during layout. We should instead compute a summary bit and
|
||||
// call it once at the end of layout.
|
||||
sky.view.scheduleFrame();
|
||||
}
|
||||
void paint(RenderNodeDisplayList canvas) { }
|
||||
|
||||
|
||||
// HIT TESTING
|
||||
|
||||
void handlePointer(sky.PointerEvent event) {
|
||||
// override this if you have a client, to hand it to the client
|
||||
// override this if you want to do anything with the pointer event
|
||||
}
|
||||
|
||||
// RenderNode subclasses are expected to have a method like the
|
||||
// following (with the signature being whatever passes for coordinates
|
||||
// for this particular class):
|
||||
// bool hitTest(HitTestResult result, { sky.Point position }) {
|
||||
// // If (x,y) is not inside this node, then return false. (You
|
||||
// // can assume that the given coordinate is inside your
|
||||
// // dimensions. You only need to check this if you're an
|
||||
// // irregular shape, e.g. if you have a hole.)
|
||||
// // Otherwise:
|
||||
// // For each child that intersects x,y, in z-order starting from the top,
|
||||
// // call hitTest() for that child, passing it /result/, and the coordinates
|
||||
// // converted to the child's coordinate origin, and stop at the first child
|
||||
// // that returns true.
|
||||
// // Then, add yourself to /result/, and return true.
|
||||
// }
|
||||
// You must not add yourself to /result/ if you return false.
|
||||
|
||||
}
|
||||
|
||||
class HitTestResult {
|
||||
final List<RenderNode> path = new List<RenderNode>();
|
||||
|
||||
RenderNode get result => path.first;
|
||||
|
||||
void add(RenderNode node) {
|
||||
path.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD
|
||||
|
||||
abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> {
|
||||
ChildType _child;
|
||||
ChildType get child => _child;
|
||||
void set child (ChildType value) {
|
||||
if (_child != null)
|
||||
dropChild(_child);
|
||||
_child = value;
|
||||
if (_child != null)
|
||||
adoptChild(_child);
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GENERIC MIXIN FOR RENDER NODES WITH A LIST OF CHILDREN
|
||||
|
||||
abstract class ContainerParentDataMixin<ChildType extends RenderNode> {
|
||||
ChildType previousSibling;
|
||||
ChildType nextSibling;
|
||||
void detachSiblings() {
|
||||
if (previousSibling != null) {
|
||||
assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>);
|
||||
assert(previousSibling != this);
|
||||
assert(previousSibling.parentData.nextSibling == this);
|
||||
previousSibling.parentData.nextSibling = nextSibling;
|
||||
}
|
||||
if (nextSibling != null) {
|
||||
assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>);
|
||||
assert(nextSibling != this);
|
||||
assert(nextSibling.parentData.previousSibling == this);
|
||||
nextSibling.parentData.previousSibling = previousSibling;
|
||||
}
|
||||
previousSibling = null;
|
||||
nextSibling = null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentDataType extends ContainerParentDataMixin<ChildType>> implements RenderNode {
|
||||
// abstract class that has only InlineNode children
|
||||
|
||||
bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
while (child.parentData.previousSibling != null) {
|
||||
assert(child.parentData.previousSibling != child);
|
||||
child = child.parentData.previousSibling;
|
||||
assert(child.parentData is ParentDataType);
|
||||
}
|
||||
return child == equals;
|
||||
}
|
||||
bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
while (child.parentData.nextSibling != null) {
|
||||
assert(child.parentData.nextSibling != child);
|
||||
child = child.parentData.nextSibling;
|
||||
assert(child.parentData is ParentDataType);
|
||||
}
|
||||
return child == equals;
|
||||
}
|
||||
|
||||
ChildType _firstChild;
|
||||
ChildType _lastChild;
|
||||
void add(ChildType child, { ChildType before }) {
|
||||
assert(child != this);
|
||||
assert(before != this);
|
||||
assert(child != before);
|
||||
assert(child != _firstChild);
|
||||
assert(child != _lastChild);
|
||||
adoptChild(child);
|
||||
assert(child.parentData is ParentDataType);
|
||||
assert(child.parentData.nextSibling == null);
|
||||
assert(child.parentData.previousSibling == null);
|
||||
if (before == null) {
|
||||
// append at the end (_lastChild)
|
||||
child.parentData.previousSibling = _lastChild;
|
||||
if (_lastChild != null) {
|
||||
assert(_lastChild.parentData is ParentDataType);
|
||||
_lastChild.parentData.nextSibling = child;
|
||||
}
|
||||
_lastChild = child;
|
||||
if (_firstChild == null)
|
||||
_firstChild = child;
|
||||
} else {
|
||||
assert(_firstChild != null);
|
||||
assert(_lastChild != null);
|
||||
assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild));
|
||||
assert(_debugUltimateNextSiblingOf(before, equals: _lastChild));
|
||||
assert(before.parentData is ParentDataType);
|
||||
if (before.parentData.previousSibling == null) {
|
||||
// insert at the start (_firstChild); we'll end up with two or more children
|
||||
assert(before == _firstChild);
|
||||
child.parentData.nextSibling = before;
|
||||
before.parentData.previousSibling = child;
|
||||
_firstChild = child;
|
||||
} else {
|
||||
// insert in the middle; we'll end up with three or more children
|
||||
// set up links from child to siblings
|
||||
child.parentData.previousSibling = before.parentData.previousSibling;
|
||||
child.parentData.nextSibling = before;
|
||||
// set up links from siblings to child
|
||||
assert(child.parentData.previousSibling.parentData is ParentDataType);
|
||||
assert(child.parentData.nextSibling.parentData is ParentDataType);
|
||||
child.parentData.previousSibling.parentData.nextSibling = child;
|
||||
child.parentData.nextSibling.parentData.previousSibling = child;
|
||||
assert(before.parentData.previousSibling == child);
|
||||
}
|
||||
}
|
||||
markNeedsLayout();
|
||||
}
|
||||
void remove(ChildType child) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
|
||||
assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
|
||||
if (child.parentData.previousSibling == null) {
|
||||
assert(_firstChild == child);
|
||||
_firstChild = child.parentData.nextSibling;
|
||||
} else {
|
||||
assert(child.parentData.previousSibling.parentData is ParentDataType);
|
||||
child.parentData.previousSibling.parentData.nextSibling = child.parentData.nextSibling;
|
||||
}
|
||||
if (child.parentData.nextSibling == null) {
|
||||
assert(_lastChild == child);
|
||||
_lastChild = child.parentData.previousSibling;
|
||||
} else {
|
||||
assert(child.parentData.nextSibling.parentData is ParentDataType);
|
||||
child.parentData.nextSibling.parentData.previousSibling = child.parentData.previousSibling;
|
||||
}
|
||||
child.parentData.previousSibling = null;
|
||||
child.parentData.nextSibling = null;
|
||||
dropChild(child);
|
||||
markNeedsLayout();
|
||||
}
|
||||
void redepthChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
redepthChild(child);
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
void attachChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
child.attach();
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
void detachChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
child.detach();
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
ChildType get firstChild => _firstChild;
|
||||
ChildType get lastChild => _lastChild;
|
||||
ChildType childAfter(ChildType child) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
return child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
64
sdk/lib/framework/rendering/render_paragraph.dart
Normal file
64
sdk/lib/framework/rendering/render_paragraph.dart
Normal file
@ -0,0 +1,64 @@
|
||||
// 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 'render_box.dart';
|
||||
import 'render_node.dart';
|
||||
|
||||
class RenderInline extends RenderNode {
|
||||
String data;
|
||||
|
||||
RenderInline(this.data);
|
||||
}
|
||||
|
||||
class RenderParagraph extends RenderBox {
|
||||
|
||||
RenderParagraph({
|
||||
String text,
|
||||
int color
|
||||
}) : _color = color {
|
||||
_layoutRoot.rootElement = _document.createElement('p');
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
final sky.Document _document = new sky.Document();
|
||||
final sky.LayoutRoot _layoutRoot = new sky.LayoutRoot();
|
||||
|
||||
String get text => (_layoutRoot.rootElement.firstChild as sky.Text).data;
|
||||
void set text (String value) {
|
||||
_layoutRoot.rootElement.setChild(_document.createText(value));
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
int _color = 0xFF000000;
|
||||
int get color => _color;
|
||||
void set color (int value) {
|
||||
if (_color != value) {
|
||||
_color = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||
assert(false);
|
||||
return null;
|
||||
// we don't currently support this for RenderParagraph
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
_layoutRoot.maxWidth = constraints.maxWidth;
|
||||
_layoutRoot.minWidth = constraints.minWidth;
|
||||
_layoutRoot.minHeight = constraints.minHeight;
|
||||
_layoutRoot.maxHeight = constraints.maxHeight;
|
||||
_layoutRoot.layout();
|
||||
size = constraints.constrain(new sky.Size(_layoutRoot.rootElement.width, _layoutRoot.rootElement.height));
|
||||
}
|
||||
|
||||
void paint(RenderNodeDisplayList canvas) {
|
||||
// _layoutRoot.rootElement.style['color'] = 'rgba(' + ...color... + ')';
|
||||
_layoutRoot.paint(canvas);
|
||||
}
|
||||
|
||||
// we should probably expose a way to do precise (inter-glpyh) hit testing
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
import '../resources/third_party/unittest/unittest.dart';
|
||||
import '../resources/unit.dart';
|
||||
import 'dart:sky' as sky;
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
import 'package:sky/framework/rendering/render_box.dart';
|
||||
|
||||
void main() {
|
||||
initUnit();
|
||||
|
||||
@ -6,7 +6,9 @@ import '../resources/third_party/unittest/unittest.dart';
|
||||
import '../resources/unit.dart';
|
||||
import 'dart:sky' as sky;
|
||||
import 'package:sky/framework/app.dart';
|
||||
import 'package:sky/framework/layout2.dart';
|
||||
import 'package:sky/framework/rendering/render_block.dart';
|
||||
import 'package:sky/framework/rendering/render_box.dart';
|
||||
import 'package:sky/framework/rendering/render_flex.dart';
|
||||
|
||||
class RenderSolidColor extends RenderDecoratedBox {
|
||||
final sky.Size desiredSize;
|
||||
@ -41,13 +43,13 @@ void main() {
|
||||
test("should flex", () {
|
||||
RenderFlex flexRoot = new RenderFlex(direction: FlexDirection.Vertical);
|
||||
|
||||
RenderNode root = new RenderDecoratedBox(
|
||||
RenderDecoratedBox root = new RenderDecoratedBox(
|
||||
decoration: new BoxDecoration(backgroundColor: 0xFF000000),
|
||||
child: flexRoot
|
||||
);
|
||||
|
||||
void addFlexChildSolidColor(RenderFlex parent, int backgroundColor, { int flex: 0 }) {
|
||||
RenderNode child = new RenderSolidColor(backgroundColor);
|
||||
RenderSolidColor child = new RenderSolidColor(backgroundColor);
|
||||
parent.add(child);
|
||||
child.parentData.flex = flex;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user