Files
sdk/client/layout/ViewLayout.dart
T
jimhug@google.com 42c584b7c4 Revert "Example showing alternate async measurement solution"
This reverts r3473 which was causing several buildbots to go red due to
breaks in swarm tests.

The issue appears to be minor - but the buildbots have been red for too long.

Review URL: https://chromiumcodereview.appspot.com//9145004

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@3474 260f80e4-7a28-3924-810f-c04153c831b5
2012-01-21 13:53:40 +00:00

238 lines
7.3 KiB
Dart

// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/** The interface that the layout algorithms use to talk to the view. */
interface Positionable {
ViewLayout get layout();
/** Gets our custom CSS properties, as provided by the CSS preprocessor. */
Map<String, String> get customStyle();
/** Gets the root DOM used for layout. */
Element get node();
/** Gets the collection of child views. */
Collection<Positionable> get childViews();
/** Causes a view to layout its children. */
void doLayout();
}
/**
* Caches the layout parameters that were specified in CSS during a layout
* computation. These values are immutable during a layout.
*/
class LayoutParams {
// TODO(jmesserly): should be const, but there's a bug in DartC preventing us
// from calling "window." in an initializer. See b/5332777
Future<CSSStyleDeclaration> style;
int get layer() => 0;
LayoutParams(Element node) {
style = node.computedStyle;
}
}
// TODO(jmesserly): enums would really help here
class Dimension {
// TODO(jmesserly): perhaps this should be X and Y
static final WIDTH = const Dimension._internal('width');
static final HEIGHT = const Dimension._internal('height');
final String name; // for debugging
const Dimension._internal(this.name);
}
class ContentSizeMode {
/** Minimum content size, e.g. min-width and min-height in CSS. */
static final MIN = const ContentSizeMode._internal('min');
/** Maximum content size, e.g. min-width and min-height in CSS. */
static final MAX = const ContentSizeMode._internal('max');
// TODO(jmesserly): we probably want some sort of "auto" or "best fit" mode
// Don't need it yet though.
final String name; // for debugging
const ContentSizeMode._internal(this.name);
}
/**
* Abstract base class for View layout. Tracks relevant layout state.
* This code was inspired by code in Android's View.java; it's needed for the
* rest of the layout system.
*/
class ViewLayout {
/**
* The layout parameters associated with this view and used by the parent
* to determine how this view should be laid out.
*/
LayoutParams layoutParams;
Future<ElementRect> _cachedViewRect;
/** The view that this layout belongs to. */
final Positionable view;
/**
* To get a perforant positioning model on top of the DOM, we read all
* properties in the first pass while computing positions. Then we have a
* second pass that actually moves everything.
*/
int _measuredLeft, _measuredTop, _measuredWidth, _measuredHeight;
ViewLayout(this.view);
/**
* Creates the appropriate view layout, depending on the properties.
*/
// TODO(jmesserly): we should support user defined layouts somehow. Perhaps
// registered with a LayoutProvider.
factory ViewLayout.fromView(Positionable view) {
if (hasCustomLayout(view)) {
return new GridLayout(view);
} else {
return new ViewLayout(view);
}
}
static bool hasCustomLayout(Positionable view) {
return view.customStyle['display'] == "-dart-grid";
}
CSSStyleDeclaration get _style() => layoutParams.style.value;
void cacheExistingBrowserLayout() {
_cachedViewRect = view.node.rect;
}
int get currentWidth() {
return _cachedViewRect.value.offset.width;
}
int get currentHeight() {
return _cachedViewRect.value.offset.height;
}
int get borderLeftWidth() => _toPixels(_style.borderLeftWidth);
int get borderTopWidth() => _toPixels(_style.borderTopWidth);
int get borderRightWidth() => _toPixels(_style.borderRightWidth);
int get borderBottomWidth() => _toPixels(_style.borderBottomWidth);
int get borderWidth() => borderLeftWidth + borderRightWidth;
int get borderHeight() => borderTopWidth + borderBottomWidth;
/** Implements the custom layout computation. */
void measureLayout(Future<Size> size, Completer<bool> changed) {
}
/**
* Positions the view within its parent container.
* Also performs a layout of its children.
*/
void setBounds(int left, int top, int width, int height) {
assert(width >= 0 && height >= 0);
_measuredLeft = left;
_measuredTop = top;
// Note: we need to save the client height
_measuredWidth = width - borderWidth;
_measuredHeight = height - borderHeight;
final completer = new Completer<Size>();
completer.complete(new Size(_measuredWidth, _measuredHeight));
measureLayout(completer.future, null);
}
/** Applies the layout to the node. */
void applyLayout() {
if (_measuredLeft != null) {
// TODO(jmesserly): benchmark the performance of this DOM interaction
final style = view.node.style;
style.position = 'absolute';
style.left = '${_measuredLeft}px';
style.top = '${_measuredTop}px';
style.width = '${_measuredWidth}px';
style.height = '${_measuredHeight}px';
style.zIndex = '${layoutParams.layer}';
_measuredLeft = null;
_measuredTop = null;
_measuredWidth = null;
_measuredHeight = null;
// Ensure we can handle our custom layout when it is a child of a
// DOM-positioned node. For example, say we have a View tree like this:
//
// ViewWithLayout <-- uses our layout engine
// childView1 <-- is positioned by our layout engine, but uses
// HTML layout internally
// childOfChild <-- uses our layout engine for its own children
if (!hasCustomLayout(view)) {
for (final child in view.childViews) {
child.doLayout();
}
}
}
}
int measureContent(ViewLayout parent, Dimension dimension,
[ContentSizeMode mode = null]) {
switch (dimension) {
case Dimension.WIDTH:
return measureWidth(parent, mode);
case Dimension.HEIGHT:
return measureHeight(parent, mode);
}
}
int measureWidth(ViewLayout parent, ContentSizeMode mode) {
final style = layoutParams.style.value;
switch (mode) {
case ContentSizeMode.MIN:
return _styleToPixels(
style.minWidth, currentWidth, parent.currentWidth);
case ContentSizeMode.MAX:
return _styleToPixels(
style.maxWidth, currentWidth, parent.currentWidth);
}
}
int measureHeight(ViewLayout parent, ContentSizeMode mode) {
final style = layoutParams.style.value;
switch (mode) {
case ContentSizeMode.MIN:
return _styleToPixels(
style.minHeight, currentHeight, parent.currentHeight);
case ContentSizeMode.MAX:
return _styleToPixels(
style.maxHeight, currentHeight, parent.currentHeight);
}
}
static int _toPixels(String style) {
if (style.endsWith('px')) {
return Math.parseInt(style.substring(0, style.length - 2));
} else {
// TODO(jmesserly): other size units
throw new UnsupportedOperationException(
'Unknown min/max content size format: "$style"');
}
}
static int _styleToPixels(String style, num size, num parentSize) {
if (style == 'none') {
// For an unset max-content size, use the actual size
return size;
}
if (style.endsWith('%')) {
num percent = Math.parseDouble(style.substring(0, style.length - 1));
return ((percent / 100) * parentSize).toInt();
}
return _toPixels(style);
}
}