auto_layout.dart 7.9 KB
Newer Older
1
// Copyright 2016 The Chromium Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Hixie's avatar
Hixie committed
5
import 'package:cassowary/cassowary.dart' as al; // "auto layout"
6 7 8

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

/// Hosts the edge parameters and vends useful methods to construct expressions
/// for constraints. Also sets up and manages implicit constraints and edit
12
/// variables.
13 14
class AutoLayoutRect {
  AutoLayoutRect() {
15 16 17 18
    _left = new al.Param();
    _right = new al.Param();
    _top = new al.Param();
    _bottom = new al.Param();
19 20
  }

21 22 23 24
  al.Param _left;
  al.Param _right;
  al.Param _top;
  al.Param _bottom;
25

26 27 28 29
  al.Param get left => _left;
  al.Param get right => _right;
  al.Param get top => _top;
  al.Param get bottom => _bottom;
30

31 32
  al.Expression get width => _right - _left;
  al.Expression get height => _bottom - _top;
33

34 35
  al.Expression get horizontalCenter => (_left + _right) / al.cm(2.0);
  al.Expression get verticalCenter => (_top + _bottom) / al.cm(2.0);
36

37
  List<al.Constraint> contains(AutoLayoutRect other) {
38 39 40 41 42 43 44 45 46 47 48 49 50 51
    return <al.Constraint>[
      other.left >= left,
      other.right <= right,
      other.top >= top,
      other.bottom <= bottom,
    ];
  }
}

class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
  AutoLayoutParentData(this._renderBox);

  final RenderBox _renderBox;

52 53 54 55
  AutoLayoutRect get rect => _rect;
  AutoLayoutRect _rect;
  void set rect(AutoLayoutRect value) {
    if (_rect == value)
56
      return;
57
    if (_rect != null)
58
      _removeImplicitConstraints();
59 60
    _rect = value;
    if (_rect != null)
61 62 63 64 65
      _addImplicitConstraints();
  }

  BoxConstraints get _constraintsFromSolver {
    return new BoxConstraints.tightFor(
66 67
      width: _rect._right.value - _rect._left.value,
      height: _rect._bottom.value - _rect._top.value
68 69 70 71
    );
  }

  Offset get _offsetFromSolver {
72
    return new Offset(_rect._left.value, _rect._top.value);
73
  }
74

75
  List<al.Constraint> _implicitConstraints;
76

77 78
  void _addImplicitConstraints() {
    assert(_renderBox != null);
79
    if (_renderBox.parent == null || _rect == null)
80
      return;
81 82
    final List<al.Constraint> implicit = _constructImplicitConstraints();
    assert(implicit != null && implicit.isNotEmpty);
83 84 85
    assert(_renderBox.parent is RenderAutoLayout);
    final RenderAutoLayout parent = _renderBox.parent;
    final al.Result result = parent._solver.addConstraints(implicit);
86
    assert(result == al.Result.success);
87
    parent.markNeedsLayout();
88 89 90
    _implicitConstraints = implicit;
  }

91 92
  void _removeImplicitConstraints() {
    assert(_renderBox != null);
93
    if (_renderBox.parent == null || _implicitConstraints == null || _implicitConstraints.isEmpty)
94 95 96 97
      return;
    assert(_renderBox.parent is RenderAutoLayout);
    final RenderAutoLayout parent = _renderBox.parent;
    final al.Result result = parent._solver.removeConstraints(_implicitConstraints);
98
    assert(result == al.Result.success);
99
    parent.markNeedsLayout();
100 101 102
    _implicitConstraints = null;
  }

103 104 105 106
  /// Returns the set of implicit constraints that need to be applied to all
  /// instances of this class when they are moved into a render object with an
  /// active solver. If no implicit constraints needs to be applied, the object
  /// may return null.
107
  List<al.Constraint> _constructImplicitConstraints() {
Hixie's avatar
Hixie committed
108
    return <al.Constraint>[
109 110 111
      _rect._left >= al.cm(0.0), // The left edge must be positive.
      _rect._right >= _rect._left, // Width must be positive.
      // Why don't we need something similar for the top and the bottom?
112 113
    ];
  }
114
}
115

116
abstract class AutoLayoutDelegate {
117 118
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
119 120
  const AutoLayoutDelegate();

121
  List<al.Constraint> getConstraints(AutoLayoutRect parent);
122
  bool shouldUpdateConstraints(AutoLayoutDelegate oldDelegate);
123 124 125 126
}

class RenderAutoLayout extends RenderBox
    with ContainerRenderObjectMixin<RenderBox, AutoLayoutParentData>,
127 128 129 130 131 132 133
         RenderBoxContainerDefaultsMixin<RenderBox, AutoLayoutParentData> {

  RenderAutoLayout({
    AutoLayoutDelegate delegate,
    List<RenderBox> children
  }) : _delegate = delegate, _needToUpdateConstraints = (delegate != null) {
    _solver.addEditVariables(<al.Variable>[
134 135 136 137
        _rect._left.variable,
        _rect._right.variable,
        _rect._top.variable,
        _rect._bottom.variable
138
      ], al.Priority.required - 1);
139 140 141 142

    addAll(children);
  }

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
  AutoLayoutDelegate get delegate => _delegate;
  AutoLayoutDelegate _delegate;
  void set delegate(AutoLayoutDelegate newDelegate) {
    if (_delegate == newDelegate)
      return;
    AutoLayoutDelegate oldDelegate = _delegate;
    _delegate = newDelegate;
    if (newDelegate == null) {
      assert(oldDelegate != null);
      _needToUpdateConstraints = true;
      markNeedsLayout();
    } else if (oldDelegate == null ||
        newDelegate.runtimeType != oldDelegate.runtimeType ||
        newDelegate.shouldUpdateConstraints(oldDelegate)) {
      _needToUpdateConstraints = true;
158 159 160 161
      markNeedsLayout();
    }
  }

162
  bool _needToUpdateConstraints;
163

164
  final AutoLayoutRect _rect = new AutoLayoutRect();
165

166 167
  final al.Solver _solver = new al.Solver();
  final List<al.Constraint> _explicitConstraints = new List<al.Constraint>();
168

169 170 171
  void _setExplicitConstraints(List<al.Constraint> constraints) {
    assert(constraints != null);
    if (constraints.isEmpty)
172 173 174 175
      return;
    if (_solver.addConstraints(constraints) == al.Result.success)
      _explicitConstraints.addAll(constraints);
  }
176

177
  void _clearExplicitConstraints() {
178 179
    if (_explicitConstraints.isEmpty)
      return;
180 181
    if (_solver.removeConstraints(_explicitConstraints) == al.Result.success)
      _explicitConstraints.clear();
182 183
  }

184
  @override
185 186 187
  void adoptChild(RenderObject child) {
    // Make sure to call super first to setup the parent data
    super.adoptChild(child);
Hixie's avatar
Hixie committed
188
    final AutoLayoutParentData childParentData = child.parentData;
189
    childParentData._addImplicitConstraints();
Hixie's avatar
Hixie committed
190
    assert(child.parentData == childParentData);
191 192
  }

193
  @override
194
  void dropChild(RenderObject child) {
Hixie's avatar
Hixie committed
195
    final AutoLayoutParentData childParentData = child.parentData;
196
    childParentData._removeImplicitConstraints();
Hixie's avatar
Hixie committed
197
    assert(child.parentData == childParentData);
198 199 200
    super.dropChild(child);
  }

201
  @override
202 203 204 205 206
  void setupParentData(RenderObject child) {
    if (child.parentData is! AutoLayoutParentData)
      child.parentData = new AutoLayoutParentData(child);
  }

207
  @override
208 209
  bool get sizedByParent => true;

210
  @override
211 212 213 214
  void performResize() {
    size = constraints.biggest;
  }

215 216
  Size _previousSize;

217
  @override
218
  void performLayout() {
219 220
    bool needToFlushUpdates = false;

221 222 223
    if (_needToUpdateConstraints) {
      _clearExplicitConstraints();
      if (_delegate != null)
224
        _setExplicitConstraints(_delegate.getConstraints(_rect));
225
      _needToUpdateConstraints = false;
226
      needToFlushUpdates = true;
227 228
    }

229 230
    if (size != _previousSize) {
      _solver
231 232 233 234
        ..suggestValueForVariable(_rect._left.variable, 0.0)
        ..suggestValueForVariable(_rect._top.variable, 0.0)
        ..suggestValueForVariable(_rect._bottom.variable, size.height)
        ..suggestValueForVariable(_rect._right.variable, size.width);
235 236
      _previousSize = size;
      needToFlushUpdates = true;
237 238
    }

239 240 241 242 243 244 245 246 247 248 249
    if (needToFlushUpdates)
      _solver.flushUpdates();

    RenderBox child = firstChild;
    while (child != null) {
      final AutoLayoutParentData childParentData = child.parentData;
      child.layout(childParentData._constraintsFromSolver);
      childParentData.offset = childParentData._offsetFromSolver;
      assert(child.parentData == childParentData);
      child = childParentData.nextSibling;
    }
250 251
  }

252
  @override
Ian Hickson's avatar
Ian Hickson committed
253
  bool hitTestChildren(HitTestResult result, { Point position }) {
Adam Barth's avatar
Adam Barth committed
254
    return defaultHitTestChildren(result, position: position);
255 256
  }

257
  @override
258 259
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
260 261
  }
}