OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 import '../node.dart'; | |
6 import '../scheduler.dart' as scheduler; | |
7 import 'dart:math' as math; | |
8 import 'dart:sky' as sky; | |
9 | |
10 class ParentData { | |
11 void detach() { | |
12 detachSiblings(); | |
13 } | |
14 void detachSiblings() { } // workaround for lack of inter-class mixins in Dart | |
15 void merge(ParentData other) { | |
16 // override this in subclasses to merge in data from other into this | |
17 assert(other.runtimeType == this.runtimeType); | |
18 } | |
19 } | |
20 | |
21 const kLayoutDirections = 4; | |
22 | |
23 double clamp({double min: 0.0, double value: 0.0, double max: double.INFINITY})
{ | |
24 assert(min != null); | |
25 assert(value != null); | |
26 assert(max != null); | |
27 return math.max(min, math.min(max, value)); | |
28 } | |
29 | |
30 class RenderNodeDisplayList extends sky.PictureRecorder { | |
31 RenderNodeDisplayList(double width, double height) : super(width, height); | |
32 void paintChild(RenderNode child, sky.Point position) { | |
33 translate(position.x, position.y); | |
34 child.paint(this); | |
35 translate(-position.x, -position.y); | |
36 } | |
37 } | |
38 | |
39 abstract class RenderNode extends AbstractNode { | |
40 | |
41 // LAYOUT | |
42 | |
43 // parentData is only for use by the RenderNode that actually lays this | |
44 // node out, and any other nodes who happen to know exactly what | |
45 // kind of node that is. | |
46 ParentData parentData; | |
47 void setParentData(RenderNode child) { | |
48 // override this to setup .parentData correctly for your class | |
49 if (child.parentData is! ParentData) | |
50 child.parentData = new ParentData(); | |
51 } | |
52 | |
53 void adoptChild(RenderNode child) { // only for use by subclasses | |
54 // call this whenever you decide a node is a child | |
55 assert(child != null); | |
56 setParentData(child); | |
57 super.adoptChild(child); | |
58 } | |
59 void dropChild(RenderNode child) { // only for use by subclasses | |
60 assert(child != null); | |
61 assert(child.parentData != null); | |
62 child.parentData.detach(); | |
63 super.dropChild(child); | |
64 } | |
65 | |
66 static List<RenderNode> _nodesNeedingLayout = new List<RenderNode>(); | |
67 static bool _debugDoingLayout = false; | |
68 bool _needsLayout = true; | |
69 bool get needsLayout => _needsLayout; | |
70 RenderNode _relayoutSubtreeRoot; | |
71 dynamic _constraints; | |
72 dynamic get constraints => _constraints; | |
73 bool debugAncestorsAlreadyMarkedNeedsLayout() { | |
74 if (_relayoutSubtreeRoot == null) | |
75 return true; // we haven't yet done layout even once, so there's nothing f
or us to do | |
76 RenderNode node = this; | |
77 while (node != _relayoutSubtreeRoot) { | |
78 assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot); | |
79 assert(node.parent != null); | |
80 node = node.parent as RenderNode; | |
81 if (!node._needsLayout) | |
82 return false; | |
83 } | |
84 assert(node._relayoutSubtreeRoot == node); | |
85 return true; | |
86 } | |
87 void markNeedsLayout() { | |
88 assert(!_debugDoingLayout); | |
89 assert(!debugDoingPaint); | |
90 if (_needsLayout) { | |
91 assert(debugAncestorsAlreadyMarkedNeedsLayout()); | |
92 return; | |
93 } | |
94 _needsLayout = true; | |
95 assert(_relayoutSubtreeRoot != null); | |
96 if (_relayoutSubtreeRoot != this) { | |
97 assert(parent is RenderNode); | |
98 parent.markNeedsLayout(); | |
99 } else { | |
100 _nodesNeedingLayout.add(this); | |
101 } | |
102 } | |
103 static void flushLayout() { | |
104 _debugDoingLayout = true; | |
105 List<RenderNode> dirtyNodes = _nodesNeedingLayout; | |
106 _nodesNeedingLayout = new List<RenderNode>(); | |
107 dirtyNodes..sort((a, b) => a.depth - b.depth)..forEach((node) { | |
108 if (node._needsLayout && node.attached) | |
109 node._doLayout(); | |
110 }); | |
111 _debugDoingLayout = false; | |
112 } | |
113 void _doLayout() { | |
114 try { | |
115 assert(_relayoutSubtreeRoot == this); | |
116 performLayout(); | |
117 } catch (e, stack) { | |
118 print('Exception raised during layout of ${this}: ${e}'); | |
119 print(stack); | |
120 return; | |
121 } | |
122 _needsLayout = false; | |
123 } | |
124 void layout(dynamic constraints, { bool parentUsesSize: false }) { | |
125 RenderNode relayoutSubtreeRoot; | |
126 if (!parentUsesSize || sizedByParent || parent is! RenderNode) | |
127 relayoutSubtreeRoot = this; | |
128 else | |
129 relayoutSubtreeRoot = parent._relayoutSubtreeRoot; | |
130 if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _r
elayoutSubtreeRoot) | |
131 return; | |
132 _constraints = constraints; | |
133 _relayoutSubtreeRoot = relayoutSubtreeRoot; | |
134 if (sizedByParent) | |
135 performResize(); | |
136 performLayout(); | |
137 _needsLayout = false; | |
138 markNeedsPaint(); | |
139 } | |
140 bool get sizedByParent => false; // return true if the constraints are the onl
y input to the sizing algorithm (in particular, child nodes have no impact) | |
141 void performResize(); // set the local dimensions, using only the constraints
(only called if sizedByParent is true) | |
142 void performLayout(); | |
143 // Override this to perform relayout without your parent's | |
144 // involvement. | |
145 // | |
146 // This is called during layout. If sizedByParent is true, then | |
147 // performLayout() should not change your dimensions, only do that | |
148 // in performResize(). If sizedByParent is false, then set both | |
149 // your dimensions and do your children's layout here. | |
150 // | |
151 // When calling layout() on your children, pass in | |
152 // "parentUsesSize: true" if your size or layout is dependent on | |
153 // your child's size. | |
154 | |
155 // when the parent has rotated (e.g. when the screen has been turned | |
156 // 90 degrees), immediately prior to layout() being called for the | |
157 // new dimensions, rotate() is called with the old and new angles. | |
158 // The next time paint() is called, the coordinate space will have | |
159 // been rotated N quarter-turns clockwise, where: | |
160 // N = newAngle-oldAngle | |
161 // ...but the rendering is expected to remain the same, pixel for | |
162 // pixel, on the output device. Then, the layout() method or | |
163 // equivalent will be invoked. | |
164 | |
165 void rotate({ | |
166 int oldAngle, // 0..3 | |
167 int newAngle, // 0..3 | |
168 Duration time | |
169 }) { } | |
170 | |
171 | |
172 // PAINTING | |
173 | |
174 static bool debugDoingPaint = false; | |
175 void markNeedsPaint() { | |
176 assert(!debugDoingPaint); | |
177 scheduler.ensureVisualUpdate(); | |
178 } | |
179 void paint(RenderNodeDisplayList canvas) { } | |
180 | |
181 | |
182 // HIT TESTING | |
183 | |
184 void handlePointer(sky.PointerEvent event) { | |
185 // override this if you have a client, to hand it to the client | |
186 // override this if you want to do anything with the pointer event | |
187 } | |
188 | |
189 // RenderNode subclasses are expected to have a method like the | |
190 // following (with the signature being whatever passes for coordinates | |
191 // for this particular class): | |
192 // bool hitTest(HitTestResult result, { sky.Point position }) { | |
193 // // If (x,y) is not inside this node, then return false. (You | |
194 // // can assume that the given coordinate is inside your | |
195 // // dimensions. You only need to check this if you're an | |
196 // // irregular shape, e.g. if you have a hole.) | |
197 // // Otherwise: | |
198 // // For each child that intersects x,y, in z-order starting from the top, | |
199 // // call hitTest() for that child, passing it /result/, and the coordinate
s | |
200 // // converted to the child's coordinate origin, and stop at the first chil
d | |
201 // // that returns true. | |
202 // // Then, add yourself to /result/, and return true. | |
203 // } | |
204 // You must not add yourself to /result/ if you return false. | |
205 | |
206 } | |
207 | |
208 class HitTestResult { | |
209 final List<RenderNode> path = new List<RenderNode>(); | |
210 | |
211 RenderNode get result => path.first; | |
212 | |
213 void add(RenderNode node) { | |
214 path.add(node); | |
215 } | |
216 } | |
217 | |
218 | |
219 // GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD | |
220 | |
221 abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> { | |
222 ChildType _child; | |
223 ChildType get child => _child; | |
224 void set child (ChildType value) { | |
225 if (_child != null) | |
226 dropChild(_child); | |
227 _child = value; | |
228 if (_child != null) | |
229 adoptChild(_child); | |
230 markNeedsLayout(); | |
231 } | |
232 } | |
233 | |
234 | |
235 // GENERIC MIXIN FOR RENDER NODES WITH A LIST OF CHILDREN | |
236 | |
237 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
238 ChildType previousSibling; | |
239 ChildType nextSibling; | |
240 void detachSiblings() { | |
241 if (previousSibling != null) { | |
242 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); | |
243 assert(previousSibling != this); | |
244 assert(previousSibling.parentData.nextSibling == this); | |
245 previousSibling.parentData.nextSibling = nextSibling; | |
246 } | |
247 if (nextSibling != null) { | |
248 assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>); | |
249 assert(nextSibling != this); | |
250 assert(nextSibling.parentData.previousSibling == this); | |
251 nextSibling.parentData.previousSibling = previousSibling; | |
252 } | |
253 previousSibling = null; | |
254 nextSibling = null; | |
255 } | |
256 } | |
257 | |
258 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData
Type extends ContainerParentDataMixin<ChildType>> implements RenderNode { | |
259 // abstract class that has only InlineNode children | |
260 | |
261 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
262 assert(child.parentData is ParentDataType); | |
263 while (child.parentData.previousSibling != null) { | |
264 assert(child.parentData.previousSibling != child); | |
265 child = child.parentData.previousSibling; | |
266 assert(child.parentData is ParentDataType); | |
267 } | |
268 return child == equals; | |
269 } | |
270 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
271 assert(child.parentData is ParentDataType); | |
272 while (child.parentData.nextSibling != null) { | |
273 assert(child.parentData.nextSibling != child); | |
274 child = child.parentData.nextSibling; | |
275 assert(child.parentData is ParentDataType); | |
276 } | |
277 return child == equals; | |
278 } | |
279 | |
280 ChildType _firstChild; | |
281 ChildType _lastChild; | |
282 void add(ChildType child, { ChildType before }) { | |
283 assert(child != this); | |
284 assert(before != this); | |
285 assert(child != before); | |
286 assert(child != _firstChild); | |
287 assert(child != _lastChild); | |
288 adoptChild(child); | |
289 assert(child.parentData is ParentDataType); | |
290 assert(child.parentData.nextSibling == null); | |
291 assert(child.parentData.previousSibling == null); | |
292 if (before == null) { | |
293 // append at the end (_lastChild) | |
294 child.parentData.previousSibling = _lastChild; | |
295 if (_lastChild != null) { | |
296 assert(_lastChild.parentData is ParentDataType); | |
297 _lastChild.parentData.nextSibling = child; | |
298 } | |
299 _lastChild = child; | |
300 if (_firstChild == null) | |
301 _firstChild = child; | |
302 } else { | |
303 assert(_firstChild != null); | |
304 assert(_lastChild != null); | |
305 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
306 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
307 assert(before.parentData is ParentDataType); | |
308 if (before.parentData.previousSibling == null) { | |
309 // insert at the start (_firstChild); we'll end up with two or more chil
dren | |
310 assert(before == _firstChild); | |
311 child.parentData.nextSibling = before; | |
312 before.parentData.previousSibling = child; | |
313 _firstChild = child; | |
314 } else { | |
315 // insert in the middle; we'll end up with three or more children | |
316 // set up links from child to siblings | |
317 child.parentData.previousSibling = before.parentData.previousSibling; | |
318 child.parentData.nextSibling = before; | |
319 // set up links from siblings to child | |
320 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
321 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
322 child.parentData.previousSibling.parentData.nextSibling = child; | |
323 child.parentData.nextSibling.parentData.previousSibling = child; | |
324 assert(before.parentData.previousSibling == child); | |
325 } | |
326 } | |
327 markNeedsLayout(); | |
328 } | |
329 void remove(ChildType child) { | |
330 assert(child.parentData is ParentDataType); | |
331 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
332 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
333 if (child.parentData.previousSibling == null) { | |
334 assert(_firstChild == child); | |
335 _firstChild = child.parentData.nextSibling; | |
336 } else { | |
337 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
338 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; | |
339 } | |
340 if (child.parentData.nextSibling == null) { | |
341 assert(_lastChild == child); | |
342 _lastChild = child.parentData.previousSibling; | |
343 } else { | |
344 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
345 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; | |
346 } | |
347 child.parentData.previousSibling = null; | |
348 child.parentData.nextSibling = null; | |
349 dropChild(child); | |
350 markNeedsLayout(); | |
351 } | |
352 void redepthChildren() { | |
353 ChildType child = _firstChild; | |
354 while (child != null) { | |
355 redepthChild(child); | |
356 assert(child.parentData is ParentDataType); | |
357 child = child.parentData.nextSibling; | |
358 } | |
359 } | |
360 void attachChildren() { | |
361 ChildType child = _firstChild; | |
362 while (child != null) { | |
363 child.attach(); | |
364 assert(child.parentData is ParentDataType); | |
365 child = child.parentData.nextSibling; | |
366 } | |
367 } | |
368 void detachChildren() { | |
369 ChildType child = _firstChild; | |
370 while (child != null) { | |
371 child.detach(); | |
372 assert(child.parentData is ParentDataType); | |
373 child = child.parentData.nextSibling; | |
374 } | |
375 } | |
376 | |
377 ChildType get firstChild => _firstChild; | |
378 ChildType get lastChild => _lastChild; | |
379 ChildType childAfter(ChildType child) { | |
380 assert(child.parentData is ParentDataType); | |
381 return child.parentData.nextSibling; | |
382 } | |
383 } | |
OLD | NEW |