mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Effen] Port fn.dart from the legacy sky.Node backend to the RenderNode backend, which is currently just a sky.Node-backed shim, but will eventually be the core Sky interface for layout and painting.
- the custom layout class in fn is removed by this patch; a new class
will be added in a later CL
- the version of layout.dart in this CL is a subset of what we're
targetting on the long run with
https://codereview.chromium.org/1093633002
- a couple of lines of dead code are removed in this CL also
R=eseidel@chromium.org
Review URL: https://codereview.chromium.org/1117143003
This commit is contained in:
parent
77803d3826
commit
e8ead8bbbe
@ -6,7 +6,6 @@ import '../animation/scroll_behavior.dart';
|
||||
import '../debug/tracing.dart';
|
||||
import '../fn.dart';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:sky' as sky;
|
||||
import 'dart:async';
|
||||
import 'scrollable.dart';
|
||||
|
||||
@ -52,14 +51,11 @@ abstract class FixedHeightScrollable extends Scrollable {
|
||||
var item = root.firstChild.firstChild;
|
||||
if (item == null)
|
||||
return;
|
||||
sky.ClientRect scrollRect = root.getBoundingClientRect();
|
||||
sky.ClientRect itemRect = item.getBoundingClientRect();
|
||||
assert(scrollRect.height > 0);
|
||||
assert(itemRect.height > 0);
|
||||
|
||||
setState(() {
|
||||
_height = scrollRect.height;
|
||||
_itemHeight = itemRect.height;
|
||||
_height = root.height;
|
||||
assert(_height > 0);
|
||||
_itemHeight = item.height;
|
||||
assert(_itemHeight > 0);
|
||||
scrollBehavior.containerHeight = _height;
|
||||
scrollBehavior.contentsHeight = _itemHeight * _itemCount;
|
||||
});
|
||||
@ -82,7 +78,6 @@ abstract class FixedHeightScrollable extends Scrollable {
|
||||
'transform: translateY(${(-scrollOffset).toStringAsFixed(2)}px)';
|
||||
} else {
|
||||
drawCount = (_height / _itemHeight).round() + 1;
|
||||
double alignmentOffset = math.max(0.0, scrollOffset);
|
||||
double alignmentDelta = -scrollOffset % _itemHeight;
|
||||
if (alignmentDelta != 0.0)
|
||||
alignmentDelta -= _itemHeight;
|
||||
|
||||
@ -8,16 +8,16 @@ import '../fn.dart';
|
||||
import '../theme/view_configuration.dart' as config;
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:sky' as sky;
|
||||
import '../layout.dart';
|
||||
|
||||
const double _kSplashConfirmedDuration = 350.0;
|
||||
const double _kSplashUnconfirmedDuration = config.kDefaultLongPressTimeout;
|
||||
const double _kSplashAbortDuration = 100.0;
|
||||
const double _kSplashInitialDelay = 0.0; // we could delay initially in case the user scrolls
|
||||
|
||||
double _getSplashTargetSize(sky.ClientRect rect, double x, double y) {
|
||||
return 2.0 * math.max(math.max(x - rect.left, rect.right - x),
|
||||
math.max(y - rect.top, rect.bottom - y));
|
||||
double _getSplashTargetSize(Rect rect, double x, double y) {
|
||||
return 2.0 * math.max(math.max(x - rect.x, rect.x + rect.width - x),
|
||||
math.max(y - rect.y, rect.y + rect.height - y));
|
||||
}
|
||||
|
||||
class SplashController {
|
||||
@ -56,10 +56,10 @@ class SplashController {
|
||||
_size.stop();
|
||||
}
|
||||
|
||||
SplashController(sky.ClientRect rect, double x, double y,
|
||||
SplashController(Rect rect, double x, double y,
|
||||
{ this.pointer, Function onDone })
|
||||
: _offsetX = x - rect.left,
|
||||
_offsetY = y - rect.top,
|
||||
: _offsetX = x - rect.x,
|
||||
_offsetY = y - rect.y,
|
||||
_targetSize = _getSplashTargetSize(rect, x, y) {
|
||||
|
||||
_styleStream = _size.onValueChanged.map((p) {
|
||||
|
||||
@ -45,14 +45,13 @@ class InkWell extends Component implements ScrollClient {
|
||||
);
|
||||
}
|
||||
|
||||
sky.ClientRect _getBoundingRect() => (getRoot() as sky.Element).getBoundingClientRect();
|
||||
|
||||
void _startSplash(sky.GestureEvent event) {
|
||||
setState(() {
|
||||
if (_splashes == null)
|
||||
_splashes = new LinkedHashSet<SplashController>();
|
||||
var splash;
|
||||
splash = new SplashController(_getBoundingRect(), event.x, event.y,
|
||||
var root = getRoot();
|
||||
splash = new SplashController(root.rect, event.x, event.y,
|
||||
pointer: event.primaryPointer,
|
||||
onDone: () { _splashDone(splash); });
|
||||
_splashes.add(splash);
|
||||
|
||||
@ -92,8 +92,8 @@ class PopupMenu extends AnimatedComponent {
|
||||
void _measureSize() {
|
||||
setState(() {
|
||||
var root = getRoot();
|
||||
_width = root.clientWidth;
|
||||
_height = root.clientHeight;
|
||||
_width = root.width.round();
|
||||
_height = root.height.round();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -8,51 +8,15 @@ import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:sky' as sky;
|
||||
import 'reflect.dart' as reflect;
|
||||
import 'layout.dart';
|
||||
|
||||
export 'layout.dart' show Style;
|
||||
|
||||
final sky.Tracing _tracing = sky.window.tracing;
|
||||
|
||||
final bool _shouldLogRenderDuration = false;
|
||||
final bool _shouldTrace = false;
|
||||
|
||||
class Style {
|
||||
final String _className;
|
||||
static final Map<String, Style> _cache = new HashMap<String, Style>();
|
||||
|
||||
static int _nextStyleId = 1;
|
||||
|
||||
static String _getNextClassName() { return "style${_nextStyleId++}"; }
|
||||
|
||||
Style extend(Style other) {
|
||||
var className = "$_className ${other._className}";
|
||||
|
||||
return _cache.putIfAbsent(className, () {
|
||||
return new Style._internal(className);
|
||||
});
|
||||
}
|
||||
|
||||
factory Style(String styles) {
|
||||
return _cache.putIfAbsent(styles, () {
|
||||
var className = _getNextClassName();
|
||||
sky.Element styleNode = sky.document.createElement('style');
|
||||
styleNode.setChild(new sky.Text(".$className { $styles }"));
|
||||
sky.document.appendChild(styleNode);
|
||||
return new Style._internal(className);
|
||||
});
|
||||
}
|
||||
|
||||
Style._internal(this._className);
|
||||
}
|
||||
|
||||
void _parentInsertBefore(sky.ParentNode parent,
|
||||
sky.Node node,
|
||||
sky.Node ref) {
|
||||
if (ref != null) {
|
||||
ref.insertBefore([node]);
|
||||
} else {
|
||||
parent.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
enum _SyncOperation { IDENTICAL, INSERTION, STATEFUL, STATELESS, REMOVAL }
|
||||
|
||||
/*
|
||||
@ -63,7 +27,7 @@ abstract class UINode {
|
||||
String _key;
|
||||
UINode _parent;
|
||||
UINode get parent => _parent;
|
||||
sky.Node _root;
|
||||
RenderCSS _root;
|
||||
bool _defunct = false;
|
||||
|
||||
UINode({ Object key }) {
|
||||
@ -74,7 +38,7 @@ abstract class UINode {
|
||||
// if the |old| node has become stateful and should be retained.
|
||||
bool _willSync(UINode old) => false;
|
||||
|
||||
void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore);
|
||||
void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore);
|
||||
|
||||
void _remove() {
|
||||
_defunct = true;
|
||||
@ -118,8 +82,8 @@ abstract class UINode {
|
||||
}
|
||||
|
||||
// Returns the child which should be retained as the child of this node.
|
||||
UINode _syncChild(UINode node, UINode oldNode, sky.ParentNode host,
|
||||
sky.Node insertBefore) {
|
||||
UINode _syncChild(UINode node, UINode oldNode, RenderCSSContainer host,
|
||||
RenderCSS insertBefore) {
|
||||
|
||||
assert(oldNode == null || node._key == oldNode._key);
|
||||
|
||||
@ -139,7 +103,7 @@ abstract class UINode {
|
||||
_traceSync(_SyncOperation.STATEFUL, node._key);
|
||||
oldNode._sync(node, host, insertBefore);
|
||||
node._defunct = true;
|
||||
assert(oldNode._root is sky.Node);
|
||||
assert(oldNode._root is RenderCSS);
|
||||
return oldNode;
|
||||
}
|
||||
|
||||
@ -154,7 +118,7 @@ abstract class UINode {
|
||||
if (oldNode != null)
|
||||
oldNode._defunct = true;
|
||||
|
||||
assert(node._root is sky.Node);
|
||||
assert(node._root is RenderCSS);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
@ -164,14 +128,16 @@ abstract class ContentNode extends UINode {
|
||||
|
||||
ContentNode(UINode content) : this.content = content, super(key: content._key);
|
||||
|
||||
void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) {
|
||||
void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) {
|
||||
UINode oldContent = old == null ? null : (old as ContentNode).content;
|
||||
content = _syncChild(content, oldContent, host, insertBefore);
|
||||
assert(content._root != null);
|
||||
_root = content._root;
|
||||
}
|
||||
|
||||
void _remove() {
|
||||
_removeChild(content);
|
||||
if (content != null)
|
||||
_removeChild(content);
|
||||
super._remove();
|
||||
}
|
||||
}
|
||||
@ -183,31 +149,33 @@ class StyleNode extends ContentNode {
|
||||
}
|
||||
|
||||
/*
|
||||
* SkyNodeWrappers correspond to a desired state of a sky.Node. They are fully
|
||||
* SkyNodeWrappers correspond to a desired state of a RenderCSS. They are fully
|
||||
* immutable, with one exception: A UINode which is a Component which lives within
|
||||
* an SkyElementWrapper's children list, may be replaced with the "old" instance if it
|
||||
* has become stateful.
|
||||
*/
|
||||
abstract class SkyNodeWrapper extends UINode {
|
||||
|
||||
static final Map<sky.Node, SkyNodeWrapper> _nodeMap =
|
||||
new HashMap<sky.Node, SkyNodeWrapper>();
|
||||
static final Map<RenderCSS, SkyNodeWrapper> _nodeMap =
|
||||
new HashMap<RenderCSS, SkyNodeWrapper>();
|
||||
|
||||
static SkyNodeWrapper _getMounted(sky.Node node) => _nodeMap[node];
|
||||
static SkyNodeWrapper _getMounted(RenderCSS node) => _nodeMap[node];
|
||||
|
||||
SkyNodeWrapper({ Object key }) : super(key: key);
|
||||
|
||||
SkyNodeWrapper get _emptyNode;
|
||||
|
||||
sky.Node _createNode();
|
||||
RenderCSS _createNode();
|
||||
|
||||
void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) {
|
||||
void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) {
|
||||
if (old == null) {
|
||||
_root = _createNode();
|
||||
_parentInsertBefore(host, _root, insertBefore);
|
||||
assert(_root != null);
|
||||
host.add(_root, before: insertBefore);
|
||||
old = _emptyNode;
|
||||
} else {
|
||||
_root = old._root;
|
||||
assert(_root != null);
|
||||
}
|
||||
|
||||
_nodeMap[_root] = this;
|
||||
@ -216,9 +184,14 @@ abstract class SkyNodeWrapper extends UINode {
|
||||
|
||||
void _syncNode(SkyNodeWrapper old);
|
||||
|
||||
void _removeChild(UINode node) {
|
||||
assert(_root is RenderCSSContainer);
|
||||
_root.remove(node._root);
|
||||
super._removeChild(node);
|
||||
}
|
||||
|
||||
void _remove() {
|
||||
assert(_root != null);
|
||||
_root.remove();
|
||||
_nodeMap.remove(_root);
|
||||
super._remove();
|
||||
}
|
||||
@ -314,12 +287,12 @@ class EventListenerNode extends ContentNode {
|
||||
}
|
||||
|
||||
static void _dispatchEvent(sky.Event e) {
|
||||
UINode target = SkyNodeWrapper._getMounted(e.target);
|
||||
UINode target = SkyNodeWrapper._getMounted(bridgeEventTargetToRenderNode(e.target));
|
||||
|
||||
// TODO(rafaelw): StopPropagation?
|
||||
while (target != null) {
|
||||
if (target is EventListenerNode) {
|
||||
(target as EventListenerNode)._handleEvent(e);
|
||||
target._handleEvent(e);
|
||||
}
|
||||
|
||||
target = target._parent;
|
||||
@ -332,7 +305,7 @@ class EventListenerNode extends ContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) {
|
||||
void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) {
|
||||
for (var type in listeners.keys) {
|
||||
_ensureDocumentListener(type);
|
||||
}
|
||||
@ -354,19 +327,15 @@ class Text extends SkyNodeWrapper {
|
||||
|
||||
SkyNodeWrapper get _emptyNode => _emptyText;
|
||||
|
||||
static final Style _displayParagraph = new Style('display:paragraph');
|
||||
|
||||
sky.Node _createNode() {
|
||||
return sky.document.createElement('div')
|
||||
..setChild(new sky.Text(this.data))
|
||||
..setAttribute('class', _displayParagraph._className);
|
||||
RenderCSSText _root;
|
||||
RenderCSS _createNode() {
|
||||
return new RenderCSSText(this, this.data);
|
||||
}
|
||||
|
||||
void _syncNode(SkyNodeWrapper old) {
|
||||
if (old == _emptyText)
|
||||
return; // we set inside _createNode();
|
||||
|
||||
(_root.firstChild as sky.Text).data = data;
|
||||
_root.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,16 +343,10 @@ final List<UINode> _emptyList = new List<UINode>();
|
||||
|
||||
abstract class SkyElementWrapper extends SkyNodeWrapper {
|
||||
|
||||
String get _tagName;
|
||||
|
||||
sky.Node _createNode() => sky.document.createElement(_tagName);
|
||||
|
||||
final List<UINode> children;
|
||||
final Style style;
|
||||
final String inlineStyle;
|
||||
|
||||
String _class;
|
||||
|
||||
SkyElementWrapper({
|
||||
Object key,
|
||||
List<UINode> children,
|
||||
@ -396,17 +359,18 @@ abstract class SkyElementWrapper extends SkyNodeWrapper {
|
||||
}
|
||||
|
||||
void _remove() {
|
||||
super._remove();
|
||||
if (children != null) {
|
||||
for (var child in children) {
|
||||
_removeChild(child);
|
||||
}
|
||||
assert(children != null);
|
||||
for (var child in children) {
|
||||
assert(child != null);
|
||||
_removeChild(child);
|
||||
}
|
||||
super._remove();
|
||||
}
|
||||
|
||||
bool _debugHasDuplicateIds() {
|
||||
var idSet = new HashSet<String>();
|
||||
for (var child in children) {
|
||||
assert(child != null);
|
||||
if (child is Text) {
|
||||
continue; // Text nodes all have the same key and are never reordered.
|
||||
}
|
||||
@ -419,42 +383,28 @@ abstract class SkyElementWrapper extends SkyNodeWrapper {
|
||||
return false;
|
||||
}
|
||||
|
||||
void _ensureClass() {
|
||||
if (_class == null) {
|
||||
List<Style> styles = new List<Style>();
|
||||
if (style != null) {
|
||||
styles.add(style);
|
||||
}
|
||||
|
||||
UINode parent = _parent;
|
||||
while (parent != null && parent is! SkyNodeWrapper) {
|
||||
if (parent is StyleNode && (parent as StyleNode).style != null)
|
||||
styles.add((parent as StyleNode).style);
|
||||
|
||||
parent = parent._parent;
|
||||
}
|
||||
|
||||
_class = styles.map((s) => s._className).join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
void _syncNode(SkyNodeWrapper old) {
|
||||
SkyElementWrapper oldSkyElementWrapper = old as SkyElementWrapper;
|
||||
sky.Element root = _root as sky.Element;
|
||||
|
||||
_ensureClass();
|
||||
if (_class != oldSkyElementWrapper._class && _class != '')
|
||||
root.setAttribute('class', _class);
|
||||
List<Style> styles = new List<Style>();
|
||||
if (style != null)
|
||||
styles.add(style);
|
||||
UINode parent = _parent;
|
||||
while (parent != null && parent is! SkyNodeWrapper) {
|
||||
if (parent is StyleNode && parent.style != null)
|
||||
styles.add(parent.style);
|
||||
parent = parent._parent;
|
||||
}
|
||||
_root.updateStyles(styles);
|
||||
|
||||
if (inlineStyle != oldSkyElementWrapper.inlineStyle)
|
||||
root.setAttribute('style', inlineStyle);
|
||||
_root.updateInlineStyle(inlineStyle);
|
||||
|
||||
_syncChildren(oldSkyElementWrapper);
|
||||
}
|
||||
|
||||
void _syncChildren(SkyElementWrapper oldSkyElementWrapper) {
|
||||
sky.Element root = _root as sky.Element;
|
||||
assert(root != null);
|
||||
if (_root is! RenderCSSContainer)
|
||||
return;
|
||||
|
||||
var startIndex = 0;
|
||||
var endIndex = children.length;
|
||||
@ -463,12 +413,13 @@ abstract class SkyElementWrapper extends SkyNodeWrapper {
|
||||
var oldStartIndex = 0;
|
||||
var oldEndIndex = oldChildren.length;
|
||||
|
||||
sky.Node nextSibling = null;
|
||||
RenderCSS nextSibling = null;
|
||||
UINode currentNode = null;
|
||||
UINode oldNode = null;
|
||||
|
||||
void sync(int atIndex) {
|
||||
children[atIndex] = _syncChild(currentNode, oldNode, _root, nextSibling);
|
||||
assert(children[atIndex] != null);
|
||||
}
|
||||
|
||||
// Scan backwards from end of list while nodes can be directly synced
|
||||
@ -484,7 +435,6 @@ abstract class SkyElementWrapper extends SkyNodeWrapper {
|
||||
endIndex--;
|
||||
oldEndIndex--;
|
||||
sync(endIndex);
|
||||
nextSibling = currentNode._root;
|
||||
}
|
||||
|
||||
HashMap<String, UINode> oldNodeIdMap = null;
|
||||
@ -526,19 +476,22 @@ abstract class SkyElementWrapper extends SkyNodeWrapper {
|
||||
return false;
|
||||
|
||||
oldNodeIdMap[currentNode._key] = null; // mark it reordered.
|
||||
_parentInsertBefore(root, oldNode._root, nextSibling);
|
||||
assert(_root is RenderCSSContainer);
|
||||
assert(oldNode._root is RenderCSSContainer);
|
||||
oldSkyElementWrapper._root.remove(oldNode._root);
|
||||
_root.add(oldNode._root, before: nextSibling);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scan forwards, this time we may re-order;
|
||||
nextSibling = root.firstChild;
|
||||
nextSibling = _root.firstChild;
|
||||
while (startIndex < endIndex && oldStartIndex < oldEndIndex) {
|
||||
currentNode = children[startIndex];
|
||||
oldNode = oldChildren[oldStartIndex];
|
||||
|
||||
if (currentNode._key == oldNode._key) {
|
||||
assert(currentNode.runtimeType == oldNode.runtimeType);
|
||||
nextSibling = nextSibling.nextSibling;
|
||||
nextSibling = _root.childAfter(nextSibling);
|
||||
sync(startIndex);
|
||||
startIndex++;
|
||||
advanceOldStartIndex();
|
||||
@ -571,7 +524,7 @@ abstract class SkyElementWrapper extends SkyNodeWrapper {
|
||||
|
||||
class Container extends SkyElementWrapper {
|
||||
|
||||
String get _tagName => 'div';
|
||||
RenderCSS _createNode() => new RenderCSSContainer(this);
|
||||
|
||||
static final Container _emptyContainer = new Container();
|
||||
|
||||
@ -590,49 +543,10 @@ class Container extends SkyElementWrapper {
|
||||
);
|
||||
}
|
||||
|
||||
abstract class LayoutContainer extends Container {
|
||||
|
||||
LayoutContainer({
|
||||
Object key,
|
||||
List<UINode> children,
|
||||
Style style,
|
||||
String inlineStyle
|
||||
}) : super(
|
||||
key: key,
|
||||
children: children,
|
||||
style: style,
|
||||
inlineStyle: inlineStyle
|
||||
);
|
||||
|
||||
sky.Node _createNode() {
|
||||
var result = super._createNode();
|
||||
result.setLayoutManager(() => layout(_root));
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we ever reuse sky nodes for different classes, then we should
|
||||
// call _root.setLayoutManager(null) during _remove() here.
|
||||
|
||||
void _syncNode(SkyNodeWrapper old) {
|
||||
super._syncNode(old);
|
||||
_root.setLayoutManager(() => layout(_root));
|
||||
_root.setNeedsLayout();
|
||||
}
|
||||
|
||||
void layout(sky.Element skyNode);
|
||||
// set skyNode.width (e.g., set it to skyNode.parentNode.width)
|
||||
// for each skyNode.getChildNodes()[i]:
|
||||
// call .layout()
|
||||
// set .x, .y
|
||||
// set .width if you want to force a width
|
||||
// set .height if you want to force a height
|
||||
// set skyNode.height
|
||||
|
||||
}
|
||||
|
||||
class Image extends SkyElementWrapper {
|
||||
|
||||
String get _tagName => 'img';
|
||||
RenderCSSImage _root;
|
||||
RenderCSSImage _createNode() => new RenderCSSImage(this, this.src, this.width, this.height);
|
||||
|
||||
static final Image _emptyImage = new Image();
|
||||
|
||||
@ -659,56 +573,7 @@ class Image extends SkyElementWrapper {
|
||||
|
||||
void _syncNode(UINode old) {
|
||||
super._syncNode(old);
|
||||
|
||||
Image oldImage = old as Image;
|
||||
sky.HTMLImageElement skyImage = _root as sky.HTMLImageElement;
|
||||
|
||||
if (src != oldImage.src)
|
||||
skyImage.src = src;
|
||||
|
||||
if (width != oldImage.width)
|
||||
skyImage.style['width'] = '${width}px';
|
||||
|
||||
if (height != oldImage.height)
|
||||
skyImage.style['height'] = '${height}px';
|
||||
}
|
||||
}
|
||||
|
||||
class Anchor extends SkyElementWrapper {
|
||||
|
||||
String get _tagName => 'a';
|
||||
|
||||
static final Anchor _emptyAnchor = new Anchor();
|
||||
|
||||
UINode get _emptyNode => _emptyAnchor;
|
||||
|
||||
final String href;
|
||||
final int width;
|
||||
final int height;
|
||||
|
||||
Anchor({
|
||||
Object key,
|
||||
List<UINode> children,
|
||||
Style style,
|
||||
String inlineStyle,
|
||||
this.width,
|
||||
this.height,
|
||||
this.href
|
||||
}) : super(
|
||||
key: key,
|
||||
children: children,
|
||||
style: style,
|
||||
inlineStyle: inlineStyle
|
||||
);
|
||||
|
||||
void _syncNode(UINode old) {
|
||||
super._syncNode(old);
|
||||
|
||||
Anchor oldAnchor = old as Anchor;
|
||||
sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement;
|
||||
|
||||
if (href != oldAnchor.href)
|
||||
skyAnchor.href = href;
|
||||
_root.configure(this.src, this.width, this.height);
|
||||
}
|
||||
}
|
||||
|
||||
@ -789,9 +654,6 @@ abstract class Component extends UINode {
|
||||
bool get _isBuilding => _currentlyBuilding == this;
|
||||
bool _dirty = true;
|
||||
|
||||
sky.Node get _host => _root.parentNode;
|
||||
sky.Node get _insertionPoint => _root == null ? _root : _root.nextSibling;
|
||||
|
||||
UINode _built;
|
||||
final int _order;
|
||||
static int _currentOrder = 0;
|
||||
@ -835,7 +697,7 @@ abstract class Component extends UINode {
|
||||
|
||||
// TODO(rafaelw): It seems wrong to expose DOM at all. This is presently
|
||||
// needed to get sizing info.
|
||||
sky.Node getRoot() => _root;
|
||||
RenderCSS getRoot() => _root;
|
||||
|
||||
void _remove() {
|
||||
assert(_built != null);
|
||||
@ -871,7 +733,7 @@ abstract class Component extends UINode {
|
||||
* 3) Syncing against an old version
|
||||
* assert(_built == null && old != null)
|
||||
*/
|
||||
void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) {
|
||||
void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) {
|
||||
assert(!_defunct);
|
||||
assert(_built == null || old == null);
|
||||
|
||||
@ -898,15 +760,15 @@ abstract class Component extends UINode {
|
||||
_built = _syncChild(_built, oldBuilt, host, insertBefore);
|
||||
_dirty = false;
|
||||
_root = _built._root;
|
||||
assert(_root != null);
|
||||
}
|
||||
|
||||
void _buildIfDirty() {
|
||||
if (!_dirty || _defunct)
|
||||
return;
|
||||
|
||||
assert(_host != null);
|
||||
_trace('$_key rebuilding...');
|
||||
_sync(null, _host, _insertionPoint);
|
||||
_sync(null, null, null); // TODO(ianh): figure out how passing "null, null, null" here is ok
|
||||
}
|
||||
|
||||
void scheduleBuild() {
|
||||
@ -927,11 +789,18 @@ abstract class Component extends UINode {
|
||||
}
|
||||
|
||||
abstract class App extends Component {
|
||||
sky.Node _host;
|
||||
RenderCSS _host;
|
||||
|
||||
App() : super(stateful: true) {
|
||||
_host = sky.document.createElement('div');
|
||||
sky.document.appendChild(_host);
|
||||
_host = new RenderCSSRoot(this);
|
||||
_scheduleComponentForRender(this);
|
||||
}
|
||||
|
||||
void _buildIfDirty() {
|
||||
if (!_dirty || _defunct)
|
||||
return;
|
||||
|
||||
_trace('$_key rebuilding...');
|
||||
_sync(null, _host, _root);
|
||||
}
|
||||
}
|
||||
|
||||
406
framework/layout.dart
Normal file
406
framework/layout.dart
Normal file
@ -0,0 +1,406 @@
|
||||
library layout;
|
||||
|
||||
import 'node.dart';
|
||||
import 'dart:sky' as sky;
|
||||
import 'dart:collection';
|
||||
|
||||
// UTILS
|
||||
|
||||
// Bridge to legacy CSS-like style specification
|
||||
// Eventually we'll replace this with something else
|
||||
class Style {
|
||||
final String _className;
|
||||
static final Map<String, Style> _cache = new HashMap<String, Style>();
|
||||
|
||||
static int _nextStyleId = 1;
|
||||
|
||||
static String _getNextClassName() { return "style${_nextStyleId++}"; }
|
||||
|
||||
Style extend(Style other) {
|
||||
var className = "$_className ${other._className}";
|
||||
|
||||
return _cache.putIfAbsent(className, () {
|
||||
return new Style._internal(className);
|
||||
});
|
||||
}
|
||||
|
||||
factory Style(String styles) {
|
||||
return _cache.putIfAbsent(styles, () {
|
||||
var className = _getNextClassName();
|
||||
sky.Element styleNode = sky.document.createElement('style');
|
||||
styleNode.setChild(new sky.Text(".$className { $styles }"));
|
||||
sky.document.appendChild(styleNode);
|
||||
return new Style._internal(className);
|
||||
});
|
||||
}
|
||||
|
||||
Style._internal(this._className);
|
||||
}
|
||||
|
||||
class Rect {
|
||||
const Rect(this.x, this.y, this.width, this.height);
|
||||
final double x;
|
||||
final double y;
|
||||
final double width;
|
||||
final double height;
|
||||
}
|
||||
|
||||
|
||||
// ABSTRACT LAYOUT
|
||||
|
||||
class ParentData {
|
||||
void detach() {
|
||||
detachSiblings();
|
||||
}
|
||||
void detachSiblings() { } // workaround for lack of inter-class mixins in Dart
|
||||
}
|
||||
|
||||
abstract class RenderNode extends Node {
|
||||
|
||||
// LAYOUT
|
||||
|
||||
// parentData is only for use by the RenderNode that actually lays this
|
||||
// node out, and any other nodes who happen to know exactly what
|
||||
// kind of node that is.
|
||||
ParentData parentData;
|
||||
void setupPos(RenderNode child) {
|
||||
// override this to setup .parentData correctly for your class
|
||||
if (child.parentData is! ParentData)
|
||||
child.parentData = new ParentData();
|
||||
}
|
||||
|
||||
void setAsChild(RenderNode child) { // only for use by subclasses
|
||||
// call this whenever you decide a node is a child
|
||||
assert(child != null);
|
||||
setupPos(child);
|
||||
super.setAsChild(child);
|
||||
}
|
||||
void dropChild(RenderNode child) { // only for use by subclasses
|
||||
assert(child != null);
|
||||
assert(child.parentData != null);
|
||||
child.parentData.detach();
|
||||
super.dropChild(child);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class RenderBox extends RenderNode { }
|
||||
|
||||
|
||||
// GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN
|
||||
|
||||
abstract class ContainerParentDataMixin<ChildType extends RenderNode> {
|
||||
ChildType previousSibling;
|
||||
ChildType nextSibling;
|
||||
void detachSiblings() {
|
||||
if (previousSibling != null) {
|
||||
assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>);
|
||||
assert(previousSibling != this);
|
||||
assert(previousSibling.parentData.nextSibling == this);
|
||||
previousSibling.parentData.nextSibling = nextSibling;
|
||||
}
|
||||
if (nextSibling != null) {
|
||||
assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>);
|
||||
assert(nextSibling != this);
|
||||
assert(nextSibling.parentData.previousSibling == this);
|
||||
nextSibling.parentData.previousSibling = previousSibling;
|
||||
}
|
||||
previousSibling = null;
|
||||
nextSibling = null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentDataType extends ContainerParentDataMixin<ChildType>> implements RenderNode {
|
||||
// abstract class that has only InlineNode children
|
||||
|
||||
bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
while (child.parentData.previousSibling != null) {
|
||||
assert(child.parentData.previousSibling != child);
|
||||
child = child.parentData.previousSibling;
|
||||
assert(child.parentData is ParentDataType);
|
||||
}
|
||||
return child == equals;
|
||||
}
|
||||
bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
while (child.parentData.nextSibling != null) {
|
||||
assert(child.parentData.nextSibling != child);
|
||||
child = child.parentData.nextSibling;
|
||||
assert(child.parentData is ParentDataType);
|
||||
}
|
||||
return child == equals;
|
||||
}
|
||||
|
||||
ChildType _firstChild;
|
||||
ChildType _lastChild;
|
||||
void add(ChildType child, { ChildType before }) {
|
||||
assert(child != this);
|
||||
assert(before != this);
|
||||
assert(child != before);
|
||||
assert(child != _firstChild);
|
||||
assert(child != _lastChild);
|
||||
setAsChild(child);
|
||||
assert(child.parentData is ParentDataType);
|
||||
assert(child.parentData.nextSibling == null);
|
||||
assert(child.parentData.previousSibling == null);
|
||||
if (before == null) {
|
||||
// append at the end (_lastChild)
|
||||
child.parentData.previousSibling = _lastChild;
|
||||
if (_lastChild != null) {
|
||||
assert(_lastChild.parentData is ParentDataType);
|
||||
_lastChild.parentData.nextSibling = child;
|
||||
}
|
||||
_lastChild = child;
|
||||
if (_firstChild == null)
|
||||
_firstChild = child;
|
||||
} else {
|
||||
assert(_firstChild != null);
|
||||
assert(_lastChild != null);
|
||||
assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild));
|
||||
assert(_debugUltimateNextSiblingOf(before, equals: _lastChild));
|
||||
assert(before.parentData is ParentDataType);
|
||||
if (before.parentData.previousSibling == null) {
|
||||
// insert at the start (_firstChild); we'll end up with two or more children
|
||||
assert(before == _firstChild);
|
||||
child.parentData.nextSibling = before;
|
||||
before.parentData.previousSibling = child;
|
||||
_firstChild = child;
|
||||
} else {
|
||||
// insert in the middle; we'll end up with three or more children
|
||||
// set up links from child to siblings
|
||||
child.parentData.previousSibling = before.parentData.previousSibling;
|
||||
child.parentData.nextSibling = before;
|
||||
// set up links from siblings to child
|
||||
assert(child.parentData.previousSibling.parentData is ParentDataType);
|
||||
assert(child.parentData.nextSibling.parentData is ParentDataType);
|
||||
child.parentData.previousSibling.parentData.nextSibling = child;
|
||||
child.parentData.nextSibling.parentData.previousSibling = child;
|
||||
assert(before.parentData.previousSibling == child);
|
||||
}
|
||||
}
|
||||
markNeedsLayout();
|
||||
}
|
||||
void remove(ChildType child) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
|
||||
assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
|
||||
if (child.parentData.previousSibling == null) {
|
||||
assert(_firstChild == child);
|
||||
_firstChild = child.parentData.nextSibling;
|
||||
} else {
|
||||
assert(child.parentData.previousSibling.parentData is ParentDataType);
|
||||
child.parentData.previousSibling.parentData.nextSibling = child.parentData.nextSibling;
|
||||
}
|
||||
if (child.parentData.nextSibling == null) {
|
||||
assert(_lastChild == child);
|
||||
_lastChild = child.parentData.previousSibling;
|
||||
} else {
|
||||
assert(child.parentData.nextSibling.parentData is ParentDataType);
|
||||
child.parentData.nextSibling.parentData.previousSibling = child.parentData.previousSibling;
|
||||
}
|
||||
child.parentData.previousSibling = null;
|
||||
child.parentData.nextSibling = null;
|
||||
dropChild(child);
|
||||
markNeedsLayout();
|
||||
}
|
||||
void redepthChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
redepthChild(child);
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
void attachChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
child.attach();
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
void detachChildren() {
|
||||
ChildType child = _firstChild;
|
||||
while (child != null) {
|
||||
child.detach();
|
||||
assert(child.parentData is ParentDataType);
|
||||
child = child.parentData.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
ChildType get firstChild => _firstChild;
|
||||
ChildType get lastChild => _lastChild;
|
||||
ChildType childAfter(ChildType child) {
|
||||
assert(child.parentData is ParentDataType);
|
||||
return child.parentData.nextSibling;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// CSS SHIMS
|
||||
|
||||
abstract class RenderCSS extends RenderBox {
|
||||
|
||||
dynamic debug;
|
||||
sky.Element _skyElement;
|
||||
|
||||
RenderCSS(this.debug) {
|
||||
_skyElement = createSkyElement();
|
||||
registerEventTarget(_skyElement, this);
|
||||
}
|
||||
|
||||
sky.Element createSkyElement();
|
||||
|
||||
void updateStyles(List<Style> styles) {
|
||||
_skyElement.setAttribute('class', styles.map((s) => s._className).join(' '));
|
||||
}
|
||||
|
||||
void updateInlineStyle(String newStyle) {
|
||||
_skyElement.setAttribute('style', newStyle);
|
||||
}
|
||||
|
||||
double get width {
|
||||
sky.ClientRect rect = _skyElement.getBoundingClientRect();
|
||||
return rect.width;
|
||||
}
|
||||
|
||||
double get height {
|
||||
sky.ClientRect rect = _skyElement.getBoundingClientRect();
|
||||
return rect.height;
|
||||
}
|
||||
|
||||
Rect get rect {
|
||||
sky.ClientRect rect = _skyElement.getBoundingClientRect();
|
||||
return new Rect(rect.left, rect.top, rect.width, rect.height);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CSSParentData extends ParentData with ContainerParentDataMixin<RenderCSS> { }
|
||||
|
||||
class RenderCSSContainer extends RenderCSS with ContainerRenderNodeMixin<RenderCSS, CSSParentData> {
|
||||
|
||||
RenderCSSContainer(debug) : super(debug);
|
||||
|
||||
void setupPos(RenderNode child) {
|
||||
if (child.parentData is! CSSParentData)
|
||||
child.parentData = new CSSParentData();
|
||||
}
|
||||
|
||||
sky.Element createSkyElement() => sky.document.createElement('div')
|
||||
..setAttribute('debug', debug.toString());
|
||||
|
||||
void markNeedsLayout() { }
|
||||
|
||||
void add(RenderCSS child, { RenderCSS before }) {
|
||||
if (before != null) {
|
||||
assert(before._skyElement.parentNode != null);
|
||||
assert(before._skyElement.parentNode == _skyElement);
|
||||
}
|
||||
super.add(child, before: before);
|
||||
if (before != null) {
|
||||
before._skyElement.insertBefore([child._skyElement]);
|
||||
assert(child._skyElement.parentNode != null);
|
||||
assert(child._skyElement.parentNode == _skyElement);
|
||||
assert(child._skyElement.parentNode == before._skyElement.parentNode);
|
||||
} else {
|
||||
_skyElement.appendChild(child._skyElement);
|
||||
}
|
||||
}
|
||||
void remove(RenderCSS child) {
|
||||
child._skyElement.remove();
|
||||
super.remove(child);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RenderCSSText extends RenderCSS {
|
||||
|
||||
RenderCSSText(debug, String newData) : super(debug) {
|
||||
data = newData;
|
||||
}
|
||||
|
||||
static final Style _displayParagraph = new Style('display:paragraph');
|
||||
|
||||
sky.Element createSkyElement() {
|
||||
return sky.document.createElement('div')
|
||||
..setChild(new sky.Text())
|
||||
..setAttribute('class', _displayParagraph._className)
|
||||
..setAttribute('debug', debug.toString());
|
||||
}
|
||||
|
||||
void set data (String value) {
|
||||
(_skyElement.firstChild as sky.Text).data = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RenderCSSImage extends RenderCSS {
|
||||
|
||||
RenderCSSImage(debug, String src, num width, num height) : super(debug) {
|
||||
configure(src, width, height);
|
||||
}
|
||||
|
||||
sky.Element createSkyElement() {
|
||||
return sky.document.createElement('img')
|
||||
..setAttribute('debug', debug.toString());
|
||||
}
|
||||
|
||||
void configure(String src, num width, num height) {
|
||||
if (_skyElement.getAttribute('src') != src)
|
||||
_skyElement.setAttribute('src', src);
|
||||
_skyElement.style['width'] = '${width}px';
|
||||
_skyElement.style['height'] = '${height}px';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RenderCSSRoot extends RenderCSSContainer {
|
||||
RenderCSSRoot(debug) : super(debug);
|
||||
sky.Element createSkyElement() {
|
||||
var result = super.createSkyElement();
|
||||
assert(result != null);
|
||||
sky.document.appendChild(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// legacy tools
|
||||
Map<sky.EventTarget, RenderNode> _eventTargetRegistry = {};
|
||||
void registerEventTarget(sky.EventTarget e, RenderNode n) {
|
||||
_eventTargetRegistry[e] = n;
|
||||
}
|
||||
RenderNode bridgeEventTargetToRenderNode(sky.EventTarget e) {
|
||||
return _eventTargetRegistry[e];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
String _attributes(node) {
|
||||
if (node is! sky.Element) return '';
|
||||
var result = '';
|
||||
var attrs = node.getAttributes();
|
||||
for (var attr in attrs)
|
||||
result += ' ${attr.name}="${attr.value}"';
|
||||
return result;
|
||||
}
|
||||
|
||||
void _serialiseDOM(node, [String prefix = '']) {
|
||||
if (node is sky.Text) {
|
||||
print(prefix + 'text: "' + node.data.replaceAll('\n', '\\n') + '"');
|
||||
return;
|
||||
}
|
||||
print(prefix + node.toString() + _attributes(node));
|
||||
var children = node.getChildNodes();
|
||||
prefix = prefix + ' ';
|
||||
for (var child in children)
|
||||
_serialiseDOM(child, prefix);
|
||||
}
|
||||
|
||||
void dumpState() {
|
||||
_serialiseDOM(sky.document);
|
||||
}
|
||||
60
framework/node.dart
Normal file
60
framework/node.dart
Normal file
@ -0,0 +1,60 @@
|
||||
library node;
|
||||
|
||||
class Node {
|
||||
|
||||
// Nodes always have a 'depth' greater than their ancestors'.
|
||||
// There's no guarantee regarding depth between siblings. The depth
|
||||
// of a node is used to ensure that nodes are processed in depth
|
||||
// order. The 'depth' of a child can be more than one greater than
|
||||
// the 'depth' of the parent, because the 'depth' values are never
|
||||
// decreased: all that matters is that it's greater than the parent.
|
||||
// Consider a tree with a root node A, a child B, and a grandchild
|
||||
// C. Initially, A will have 'depth' 0, B 'depth' 1, and C 'depth'
|
||||
// 2. If C is moved to be a child of A, sibling of B, then the
|
||||
// numbers won't change. C's 'depth' will still be 2.
|
||||
|
||||
int _depth = 0;
|
||||
int get depth => _depth;
|
||||
void redepthChild(Node child) { // internal, do not call
|
||||
assert(child._attached == _attached);
|
||||
if (child._depth <= _depth) {
|
||||
child._depth = _depth + 1;
|
||||
child.redepthChildren();
|
||||
}
|
||||
}
|
||||
void redepthChildren() { // internal, do not call
|
||||
// override this in subclasses with child nodes
|
||||
// simply call redepthChild(child) for each child
|
||||
}
|
||||
|
||||
bool _attached = false;
|
||||
bool get attached => _attached;
|
||||
void attach() {
|
||||
// override this in subclasses with child nodes
|
||||
// simply call attach() for each child then call your superclass
|
||||
_attached = true;
|
||||
attachChildren();
|
||||
}
|
||||
attachChildren() { } // workaround for lack of inter-class mixins in Dart
|
||||
void detach() {
|
||||
// override this in subclasses with child nodes
|
||||
// simply call detach() for each child then call your superclass
|
||||
_attached = false;
|
||||
detachChildren();
|
||||
}
|
||||
detachChildren() { } // workaround for lack of inter-class mixins in Dart
|
||||
|
||||
void setAsChild(Node child) { // only for use by subclasses
|
||||
assert(child != null);
|
||||
if (attached)
|
||||
child.attach();
|
||||
redepthChild(child);
|
||||
}
|
||||
void dropChild(Node child) { // only for use by subclasses
|
||||
assert(child != null);
|
||||
assert(child.attached == attached);
|
||||
if (attached)
|
||||
child.detach();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user