auto_layout.dart 7.55 KB
Newer Older
1 2 3 4 5
// 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 'package:cassowary/cassowary.dart' as al;
6 7
import 'package:sky/src/rendering/box.dart';
import 'package:sky/src/rendering/object.dart';
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128

/// 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
/// of render boxes taking part in auto layout
abstract class _AutoLayoutParamMixin {
  // Ideally, the edges would all be final, but then they would have to be
  // initialized before the constructor. Not sure how to do that using a Mixin
  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 _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);
  }

  void _setupEditVariablesInSolver(al.Solver solver, double priority) {
    solver.addEditVariables([
        _leftEdge.variable,
        _rightEdge.variable,
        _topEdge.variable,
        _bottomEdge.variable], priority);
  }

  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);
  }

  /// 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
  /// it other properties (if necessary)
  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;
  }
}

class AutoLayoutParentData extends BoxParentData
    with ContainerParentDataMixin<RenderBox>, _AutoLayoutParamMixin {

  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);
    assert(_renderBox.parent is RenderAutoLayout);
    assert((_renderBox.parent as RenderAutoLayout).debugDoingThisLayout); // TODO(ianh): Remove cast once the analyzer is cleverer
    BoxConstraints size = new BoxConstraints.tightFor(
      width: _rightEdge.value - _leftEdge.value,
      height: _bottomEdge.value - _topEdge.value
    );
    _renderBox.layout(size);
    position = new Point(_leftEdge.value, _topEdge.value);
  }

  List<al.Constraint> _constructImplicitConstraints() {
    return [
      _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 {

129
  RenderAutoLayout({ List<RenderBox> children }) {
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 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 180 181 182 183 184 185 186 187 188 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
    _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
  /// added or none
  al.Result addConstraints(List<al.Constraint> constraints) {
    al.Result result = _solver.addConstraints(constraints);
    if (result == al.Result.success) {
      markNeedsLayout();
      _explicitConstraints.addAll(constraints);
    }
    return result;
  }

  /// Add the given constraint to the solver.
  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);
    child.parentData._setupImplicitConstraints(_solver);
  }

  void dropChild(RenderObject child) {
    child.parentData._removeImplicitConstraints(_solver);
    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.
  }

  void hitTestChildren(HitTestResult result, {Point position}) {
    defaultHitTestChildren(result, position: position);
  }

219 220
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
221 222 223 224 225 226 227 228 229
  }

  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;
  }
}