| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /** The interface that the layout algorithms use to talk to the view. */ | |
| 6 interface Positionable { | |
| 7 ViewLayout get layout(); | |
| 8 | |
| 9 /** Gets our custom CSS properties, as provided by the CSS preprocessor. */ | |
| 10 Map<String, String> get customStyle(); | |
| 11 | |
| 12 /** Gets the root DOM used for layout. */ | |
| 13 Element get node(); | |
| 14 | |
| 15 /** Gets the collection of child views. */ | |
| 16 Collection<Positionable> get childViews(); | |
| 17 | |
| 18 /** Causes a view to layout its children. */ | |
| 19 void doLayout(); | |
| 20 } | |
| 21 | |
| 22 | |
| 23 /** | |
| 24 * Caches the layout parameters that were specified in CSS during a layout | |
| 25 * computation. These values are immutable during a layout. | |
| 26 */ | |
| 27 class LayoutParams { | |
| 28 // TODO(jmesserly): should be const, but there's a bug in DartC preventing us | |
| 29 // from calling "window." in an initializer. See b/5332777 | |
| 30 Future<CSSStyleDeclaration> style; | |
| 31 | |
| 32 int get layer() => 0; | |
| 33 | |
| 34 LayoutParams(Element node) { | |
| 35 style = node.computedStyle; | |
| 36 } | |
| 37 } | |
| 38 | |
| 39 // TODO(jmesserly): enums would really help here | |
| 40 class Dimension { | |
| 41 // TODO(jmesserly): perhaps this should be X and Y | |
| 42 static final WIDTH = const Dimension._internal('width'); | |
| 43 static final HEIGHT = const Dimension._internal('height'); | |
| 44 | |
| 45 final String name; // for debugging | |
| 46 const Dimension._internal(this.name); | |
| 47 } | |
| 48 | |
| 49 class ContentSizeMode { | |
| 50 /** Minimum content size, e.g. min-width and min-height in CSS. */ | |
| 51 static final MIN = const ContentSizeMode._internal('min'); | |
| 52 | |
| 53 /** Maximum content size, e.g. min-width and min-height in CSS. */ | |
| 54 static final MAX = const ContentSizeMode._internal('max'); | |
| 55 | |
| 56 // TODO(jmesserly): we probably want some sort of "auto" or "best fit" mode | |
| 57 // Don't need it yet though. | |
| 58 | |
| 59 final String name; // for debugging | |
| 60 const ContentSizeMode._internal(this.name); | |
| 61 } | |
| 62 | |
| 63 /** | |
| 64 * Abstract base class for View layout. Tracks relevant layout state. | |
| 65 * This code was inspired by code in Android's View.java; it's needed for the | |
| 66 * rest of the layout system. | |
| 67 */ | |
| 68 class ViewLayout { | |
| 69 /** | |
| 70 * The layout parameters associated with this view and used by the parent | |
| 71 * to determine how this view should be laid out. | |
| 72 */ | |
| 73 LayoutParams layoutParams; | |
| 74 Future<ElementRect> _cachedViewRect; | |
| 75 | |
| 76 /** The view that this layout belongs to. */ | |
| 77 final Positionable view; | |
| 78 | |
| 79 /** | |
| 80 * To get a perforant positioning model on top of the DOM, we read all | |
| 81 * properties in the first pass while computing positions. Then we have a | |
| 82 * second pass that actually moves everything. | |
| 83 */ | |
| 84 int _measuredLeft, _measuredTop, _measuredWidth, _measuredHeight; | |
| 85 | |
| 86 ViewLayout(this.view); | |
| 87 | |
| 88 /** | |
| 89 * Creates the appropriate view layout, depending on the properties. | |
| 90 */ | |
| 91 // TODO(jmesserly): we should support user defined layouts somehow. Perhaps | |
| 92 // registered with a LayoutProvider. | |
| 93 factory ViewLayout.fromView(Positionable view) { | |
| 94 if (hasCustomLayout(view)) { | |
| 95 return new GridLayout(view); | |
| 96 } else { | |
| 97 return new ViewLayout(view); | |
| 98 } | |
| 99 } | |
| 100 | |
| 101 static bool hasCustomLayout(Positionable view) { | |
| 102 return view.customStyle['display'] == "-dart-grid"; | |
| 103 } | |
| 104 | |
| 105 CSSStyleDeclaration get _style() => layoutParams.style.value; | |
| 106 | |
| 107 void cacheExistingBrowserLayout() { | |
| 108 _cachedViewRect = view.node.rect; | |
| 109 } | |
| 110 | |
| 111 int get currentWidth() { | |
| 112 return _cachedViewRect.value.offset.width; | |
| 113 } | |
| 114 | |
| 115 int get currentHeight() { | |
| 116 return _cachedViewRect.value.offset.height; | |
| 117 } | |
| 118 | |
| 119 int get borderLeftWidth() => _toPixels(_style.borderLeftWidth); | |
| 120 int get borderTopWidth() => _toPixels(_style.borderTopWidth); | |
| 121 int get borderRightWidth() => _toPixels(_style.borderRightWidth); | |
| 122 int get borderBottomWidth() => _toPixels(_style.borderBottomWidth); | |
| 123 int get borderWidth() => borderLeftWidth + borderRightWidth; | |
| 124 int get borderHeight() => borderTopWidth + borderBottomWidth; | |
| 125 | |
| 126 /** Implements the custom layout computation. */ | |
| 127 void measureLayout(Future<Size> size, Completer<bool> changed) { | |
| 128 } | |
| 129 | |
| 130 /** | |
| 131 * Positions the view within its parent container. | |
| 132 * Also performs a layout of its children. | |
| 133 */ | |
| 134 void setBounds(int left, int top, int width, int height) { | |
| 135 assert(width >= 0 && height >= 0); | |
| 136 | |
| 137 _measuredLeft = left; | |
| 138 _measuredTop = top; | |
| 139 | |
| 140 // Note: we need to save the client height | |
| 141 _measuredWidth = width - borderWidth; | |
| 142 _measuredHeight = height - borderHeight; | |
| 143 final completer = new Completer<Size>(); | |
| 144 completer.complete(new Size(_measuredWidth, _measuredHeight)); | |
| 145 measureLayout(completer.future, null); | |
| 146 } | |
| 147 | |
| 148 /** Applies the layout to the node. */ | |
| 149 void applyLayout() { | |
| 150 if (_measuredLeft != null) { | |
| 151 // TODO(jmesserly): benchmark the performance of this DOM interaction | |
| 152 final style = view.node.style; | |
| 153 style.position = 'absolute'; | |
| 154 style.left = '${_measuredLeft}px'; | |
| 155 style.top = '${_measuredTop}px'; | |
| 156 style.width = '${_measuredWidth}px'; | |
| 157 style.height = '${_measuredHeight}px'; | |
| 158 style.zIndex = '${layoutParams.layer}'; | |
| 159 | |
| 160 _measuredLeft = null; | |
| 161 _measuredTop = null; | |
| 162 _measuredWidth = null; | |
| 163 _measuredHeight = null; | |
| 164 | |
| 165 // Ensure we can handle our custom layout when it is a child of a | |
| 166 // DOM-positioned node. For example, say we have a View tree like this: | |
| 167 // | |
| 168 // ViewWithLayout <-- uses our layout engine | |
| 169 // childView1 <-- is positioned by our layout engine, but uses | |
| 170 // HTML layout internally | |
| 171 // childOfChild <-- uses our layout engine for its own children | |
| 172 if (!hasCustomLayout(view)) { | |
| 173 for (final child in view.childViews) { | |
| 174 child.doLayout(); | |
| 175 } | |
| 176 } | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 int measureContent(ViewLayout parent, Dimension dimension, | |
| 181 [ContentSizeMode mode = null]) { | |
| 182 switch (dimension) { | |
| 183 case Dimension.WIDTH: | |
| 184 return measureWidth(parent, mode); | |
| 185 case Dimension.HEIGHT: | |
| 186 return measureHeight(parent, mode); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 int measureWidth(ViewLayout parent, ContentSizeMode mode) { | |
| 191 final style = layoutParams.style.value; | |
| 192 switch (mode) { | |
| 193 case ContentSizeMode.MIN: | |
| 194 return _styleToPixels( | |
| 195 style.minWidth, currentWidth, parent.currentWidth); | |
| 196 | |
| 197 case ContentSizeMode.MAX: | |
| 198 return _styleToPixels( | |
| 199 style.maxWidth, currentWidth, parent.currentWidth); | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 int measureHeight(ViewLayout parent, ContentSizeMode mode) { | |
| 204 final style = layoutParams.style.value; | |
| 205 switch (mode) { | |
| 206 case ContentSizeMode.MIN: | |
| 207 return _styleToPixels( | |
| 208 style.minHeight, currentHeight, parent.currentHeight); | |
| 209 | |
| 210 case ContentSizeMode.MAX: | |
| 211 return _styleToPixels( | |
| 212 style.maxHeight, currentHeight, parent.currentHeight); | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 static int _toPixels(String style) { | |
| 217 if (style.endsWith('px')) { | |
| 218 return Math.parseInt(style.substring(0, style.length - 2)); | |
| 219 } else { | |
| 220 // TODO(jmesserly): other size units | |
| 221 throw new UnsupportedOperationException( | |
| 222 'Unknown min/max content size format: "$style"'); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 static int _styleToPixels(String style, num size, num parentSize) { | |
| 227 if (style == 'none') { | |
| 228 // For an unset max-content size, use the actual size | |
| 229 return size; | |
| 230 } | |
| 231 if (style.endsWith('%')) { | |
| 232 num percent = Math.parseDouble(style.substring(0, style.length - 1)); | |
| 233 return ((percent / 100) * parentSize).toInt(); | |
| 234 } | |
| 235 return _toPixels(style); | |
| 236 } | |
| 237 } | |
| OLD | NEW |