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

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 12

/// Hosts the edge parameters and vends useful methods to construct expressions
/// for constraints. Also sets up and manages implicit constraints and edit
/// variables. Used as a mixin by layout containers and parent data instances
Florian Loitsch's avatar
Florian Loitsch committed
13
/// of render boxes taking part in auto layout.
14
abstract class _AutoLayoutParamMixin {
15 16 17 18 19 20 21 22

  void _setupLayoutParameters(dynamic context) {
    _leftEdge = new al.Param.withContext(context);
    _rightEdge = new al.Param.withContext(context);
    _topEdge = new al.Param.withContext(context);
    _bottomEdge = new al.Param.withContext(context);
  }

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
  al.Param _leftEdge;
  al.Param _rightEdge;
  al.Param _topEdge;
  al.Param _bottomEdge;

  List<al.Constraint> _implicitConstraints;

  al.Param get leftEdge => _leftEdge;
  al.Param get rightEdge => _rightEdge;
  al.Param get topEdge => _topEdge;
  al.Param get bottomEdge => _bottomEdge;

  al.Expression get width => _rightEdge - _leftEdge;
  al.Expression get height => _bottomEdge - _topEdge;

  al.Expression get horizontalCenter => (_leftEdge + _rightEdge) / al.cm(2.0);
  al.Expression get verticalCenter => (_topEdge + _bottomEdge) / al.cm(2.0);

  void _setupEditVariablesInSolver(al.Solver solver, double priority) {
Hixie's avatar
Hixie committed
42
    solver.addEditVariables(<al.Variable>[
43 44 45
        _leftEdge.variable,
        _rightEdge.variable,
        _topEdge.variable,
Hixie's avatar
Hixie committed
46 47
        _bottomEdge.variable
      ], priority);
48 49 50 51 52 53 54 55 56
  }

  void _applyEditsAtSize(al.Solver solver, Size size) {
    solver.suggestValueForVariable(_leftEdge.variable, 0.0);
    solver.suggestValueForVariable(_topEdge.variable, 0.0);
    solver.suggestValueForVariable(_bottomEdge.variable, size.height);
    solver.suggestValueForVariable(_rightEdge.variable, size.width);
  }

Florian Loitsch's avatar
Florian Loitsch committed
57 58 59 60 61
  /// Applies the parameter updates.
  ///
  /// This method is called when the solver has updated at least one of the
  /// layout parameters of this object. The object is now responsible for
  /// applying this update to its other properties (if necessary).
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
  void _applyAutolayoutParameterUpdates();

  /// 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.
  List<al.Constraint> _constructImplicitConstraints();

  void _setupImplicitConstraints(al.Solver solver) {
    List<al.Constraint> implicit = _constructImplicitConstraints();

    if (implicit == null || implicit.length == 0) {
      return;
    }

    al.Result result = solver.addConstraints(implicit);
    assert(result == al.Result.success);

    _implicitConstraints = implicit;
  }

  void _removeImplicitConstraints(al.Solver solver) {
    if (_implicitConstraints == null || _implicitConstraints.length == 0) {
      return;
    }

    al.Result result = solver.removeConstraints(_implicitConstraints);
    assert(result == al.Result.success);

    _implicitConstraints = null;
  }
}

Hixie's avatar
Hixie committed
95
class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> with _AutoLayoutParamMixin {
96 97 98 99 100 101 102 103 104 105 106

  AutoLayoutParentData(this._renderBox) {
    _setupLayoutParameters(this);
  }

  final RenderBox _renderBox;

  void _applyAutolayoutParameterUpdates() {
    // This is called by the parent's layout function
    // to lay our box out.
    assert(_renderBox.parentData == this);
Hixie's avatar
Hixie committed
107 108 109 110
    assert(() {
      final RenderAutoLayout parent = _renderBox.parent;
      assert(parent.debugDoingThisLayout);
    });
111 112 113 114 115
    BoxConstraints size = new BoxConstraints.tightFor(
      width: _rightEdge.value - _leftEdge.value,
      height: _bottomEdge.value - _topEdge.value
    );
    _renderBox.layout(size);
116
    offset = new Offset(_leftEdge.value, _topEdge.value);
117 118 119
  }

  List<al.Constraint> _constructImplicitConstraints() {
Hixie's avatar
Hixie committed
120
    return <al.Constraint>[
121 122 123 124 125 126 127 128 129 130 131 132
      _leftEdge >= al.cm(0.0), // The left edge must be positive.
      _rightEdge >= _leftEdge, // Width must be positive.
    ];
  }

}

class RenderAutoLayout extends RenderBox
    with ContainerRenderObjectMixin<RenderBox, AutoLayoutParentData>,
         RenderBoxContainerDefaultsMixin<RenderBox, AutoLayoutParentData>,
         _AutoLayoutParamMixin {

133
  RenderAutoLayout({ List<RenderBox> children }) {
134 135 136 137 138 139 140 141 142
    _setupLayoutParameters(this);
    _setupEditVariablesInSolver(_solver, al.Priority.required - 1);
    addAll(children);
  }

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

  /// Adds all the given constraints to the solver. Either all constraints are
Florian Loitsch's avatar
Florian Loitsch committed
143
  /// added or none.
144 145 146 147 148 149 150 151 152
  al.Result addConstraints(List<al.Constraint> constraints) {
    al.Result result = _solver.addConstraints(constraints);
    if (result == al.Result.success) {
      markNeedsLayout();
      _explicitConstraints.addAll(constraints);
    }
    return result;
  }

Florian Loitsch's avatar
Florian Loitsch committed
153
  /// Adds the given constraint to the solver.
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
  al.Result addConstraint(al.Constraint constraint) {
    al.Result result = _solver.addConstraint(constraint);

    if (result == al.Result.success) {
      markNeedsLayout();
      _explicitConstraints.add(constraint);
    }

    return result;
  }

  /// Removes all explicitly added constraints.
  al.Result clearAllConstraints() {
    al.Result result = _solver.removeConstraints(_explicitConstraints);

    if (result == al.Result.success) {
      markNeedsLayout();
      _explicitConstraints = new List<al.Constraint>();
    }

    return result;
  }

  void adoptChild(RenderObject child) {
    // Make sure to call super first to setup the parent data
    super.adoptChild(child);
Hixie's avatar
Hixie committed
180 181 182
    final AutoLayoutParentData childParentData = child.parentData;
    childParentData._setupImplicitConstraints(_solver);
    assert(child.parentData == childParentData);
183 184 185
  }

  void dropChild(RenderObject child) {
Hixie's avatar
Hixie committed
186 187 188
    final AutoLayoutParentData childParentData = child.parentData;
    childParentData._removeImplicitConstraints(_solver);
    assert(child.parentData == childParentData);
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
    super.dropChild(child);
  }

  void setupParentData(RenderObject child) {
    if (child.parentData is! AutoLayoutParentData)
      child.parentData = new AutoLayoutParentData(child);
  }

  bool get sizedByParent => true;

  void performResize() {
    size = constraints.biggest;
  }

  void performLayout() {
    // Step 1: Update dimensions of self
    _applyEditsAtSize(_solver, size);

    // Step 2: Resolve solver updates and flush parameters

    // We don't iterate over the children, instead, we ask the solver to tell
    // us the updated parameters. Attached to the parameters (via the context)
    // are the _AutoLayoutParamMixin instances.
    for (_AutoLayoutParamMixin update in _solver.flushUpdates()) {
      update._applyAutolayoutParameterUpdates();
    }
  }

  void _applyAutolayoutParameterUpdates() {
    // Nothing to do since the size update has already been presented to the
    // solver as an edit variable modification. The invokation of this method
    // only indicates that the value has been flushed to the variable.
  }

Ian Hickson's avatar
Ian Hickson committed
223
  bool hitTestChildren(HitTestResult result, { Point position }) {
Adam Barth's avatar
Adam Barth committed
224
    return defaultHitTestChildren(result, position: position);
225 226
  }

227 228
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
229 230 231 232 233 234 235 236 237
  }

  List<al.Constraint> _constructImplicitConstraints() {
    // Only edits variables are present on layout containers. If, in the future,
    // implicit constraints (for say margins, padding, etc.) need to be added,
    // they must be returned from here.
    return null;
  }
}