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 scheduler.ensureVisualUpdate(); | |
102 } | |
103 } | |
104 static void flushLayout() { | |
105 _debugDoingLayout = true; | |
106 List<RenderNode> dirtyNodes = _nodesNeedingLayout; | |
107 _nodesNeedingLayout = new List<RenderNode>(); | |
108 dirtyNodes..sort((a, b) => a.depth - b.depth)..forEach((node) { | |
109 if (node._needsLayout && node.attached) | |
110 node._doLayout(); | |
111 }); | |
112 _debugDoingLayout = false; | |
113 } | |
114 void _doLayout() { | |
115 try { | |
116 assert(_relayoutSubtreeRoot == this); | |
117 performLayout(); | |
118 } catch (e, stack) { | |
119 print('Exception raised during layout of ${this}: ${e}'); | |
120 print(stack); | |
121 return; | |
122 } | |
123 _needsLayout = false; | |
124 } | |
125 void layout(dynamic constraints, { bool parentUsesSize: false }) { | |
126 RenderNode relayoutSubtreeRoot; | |
127 if (!parentUsesSize || sizedByParent || parent is! RenderNode) | |
128 relayoutSubtreeRoot = this; | |
129 else | |
130 relayoutSubtreeRoot = parent._relayoutSubtreeRoot; | |
131 if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _r
elayoutSubtreeRoot) | |
132 return; | |
133 _constraints = constraints; | |
134 _relayoutSubtreeRoot = relayoutSubtreeRoot; | |
135 if (sizedByParent) | |
136 performResize(); | |
137 performLayout(); | |
138 _needsLayout = false; | |
139 markNeedsPaint(); | |
140 } | |
141 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) | |
142 void performResize(); // set the local dimensions, using only the constraints
(only called if sizedByParent is true) | |
143 void performLayout(); | |
144 // Override this to perform relayout without your parent's | |
145 // involvement. | |
146 // | |
147 // This is called during layout. If sizedByParent is true, then | |
148 // performLayout() should not change your dimensions, only do that | |
149 // in performResize(). If sizedByParent is false, then set both | |
150 // your dimensions and do your children's layout here. | |
151 // | |
152 // When calling layout() on your children, pass in | |
153 // "parentUsesSize: true" if your size or layout is dependent on | |
154 // your child's size. | |
155 | |
156 // when the parent has rotated (e.g. when the screen has been turned | |
157 // 90 degrees), immediately prior to layout() being called for the | |
158 // new dimensions, rotate() is called with the old and new angles. | |
159 // The next time paint() is called, the coordinate space will have | |
160 // been rotated N quarter-turns clockwise, where: | |
161 // N = newAngle-oldAngle | |
162 // ...but the rendering is expected to remain the same, pixel for | |
163 // pixel, on the output device. Then, the layout() method or | |
164 // equivalent will be invoked. | |
165 | |
166 void rotate({ | |
167 int oldAngle, // 0..3 | |
168 int newAngle, // 0..3 | |
169 Duration time | |
170 }) { } | |
171 | |
172 | |
173 // PAINTING | |
174 | |
175 static bool debugDoingPaint = false; | |
176 void markNeedsPaint() { | |
177 assert(!debugDoingPaint); | |
178 scheduler.ensureVisualUpdate(); | |
179 } | |
180 void paint(RenderNodeDisplayList canvas) { } | |
181 | |
182 | |
183 // EVENTS | |
184 | |
185 void handleEvent(sky.Event event) { | |
186 // override this if you have a client, to hand it to the client | |
187 // override this if you want to do anything with the event | |
188 } | |
189 | |
190 | |
191 // HIT TESTING | |
192 | |
193 // RenderNode subclasses are expected to have a method like the | |
194 // following (with the signature being whatever passes for coordinates | |
195 // for this particular class): | |
196 // bool hitTest(HitTestResult result, { sky.Point position }) { | |
197 // // If (x,y) is not inside this node, then return false. (You | |
198 // // can assume that the given coordinate is inside your | |
199 // // dimensions. You only need to check this if you're an | |
200 // // irregular shape, e.g. if you have a hole.) | |
201 // // Otherwise: | |
202 // // For each child that intersects x,y, in z-order starting from the top, | |
203 // // call hitTest() for that child, passing it /result/, and the coordinate
s | |
204 // // converted to the child's coordinate origin, and stop at the first chil
d | |
205 // // that returns true. | |
206 // // Then, add yourself to /result/, and return true. | |
207 // } | |
208 // You must not add yourself to /result/ if you return false. | |
209 | |
210 } | |
211 | |
212 class HitTestResult { | |
213 final List<RenderNode> path = new List<RenderNode>(); | |
214 | |
215 RenderNode get result => path.first; | |
216 | |
217 void add(RenderNode node) { | |
218 path.add(node); | |
219 } | |
220 } | |
221 | |
222 | |
223 // GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD | |
224 | |
225 abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> { | |
226 ChildType _child; | |
227 ChildType get child => _child; | |
228 void set child (ChildType value) { | |
229 if (_child != null) | |
230 dropChild(_child); | |
231 _child = value; | |
232 if (_child != null) | |
233 adoptChild(_child); | |
234 markNeedsLayout(); | |
235 } | |
236 void attachChildren() { | |
237 if (_child != null) | |
238 _child.attach(); | |
239 } | |
240 void detachChildren() { | |
241 if (_child != null) | |
242 _child.detach(); | |
243 } | |
244 } | |
245 | |
246 | |
247 // GENERIC MIXIN FOR RENDER NODES WITH A LIST OF CHILDREN | |
248 | |
249 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
250 ChildType previousSibling; | |
251 ChildType nextSibling; | |
252 void detachSiblings() { | |
253 if (previousSibling != null) { | |
254 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); | |
255 assert(previousSibling != this); | |
256 assert(previousSibling.parentData.nextSibling == this); | |
257 previousSibling.parentData.nextSibling = nextSibling; | |
258 } | |
259 if (nextSibling != null) { | |
260 assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>); | |
261 assert(nextSibling != this); | |
262 assert(nextSibling.parentData.previousSibling == this); | |
263 nextSibling.parentData.previousSibling = previousSibling; | |
264 } | |
265 previousSibling = null; | |
266 nextSibling = null; | |
267 } | |
268 } | |
269 | |
270 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData
Type extends ContainerParentDataMixin<ChildType>> implements RenderNode { | |
271 // abstract class that has only InlineNode children | |
272 | |
273 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
274 assert(child.parentData is ParentDataType); | |
275 while (child.parentData.previousSibling != null) { | |
276 assert(child.parentData.previousSibling != child); | |
277 child = child.parentData.previousSibling; | |
278 assert(child.parentData is ParentDataType); | |
279 } | |
280 return child == equals; | |
281 } | |
282 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
283 assert(child.parentData is ParentDataType); | |
284 while (child.parentData.nextSibling != null) { | |
285 assert(child.parentData.nextSibling != child); | |
286 child = child.parentData.nextSibling; | |
287 assert(child.parentData is ParentDataType); | |
288 } | |
289 return child == equals; | |
290 } | |
291 | |
292 ChildType _firstChild; | |
293 ChildType _lastChild; | |
294 void add(ChildType child, { ChildType before }) { | |
295 assert(child != this); | |
296 assert(before != this); | |
297 assert(child != before); | |
298 assert(child != _firstChild); | |
299 assert(child != _lastChild); | |
300 adoptChild(child); | |
301 assert(child.parentData is ParentDataType); | |
302 assert(child.parentData.nextSibling == null); | |
303 assert(child.parentData.previousSibling == null); | |
304 if (before == null) { | |
305 // append at the end (_lastChild) | |
306 child.parentData.previousSibling = _lastChild; | |
307 if (_lastChild != null) { | |
308 assert(_lastChild.parentData is ParentDataType); | |
309 _lastChild.parentData.nextSibling = child; | |
310 } | |
311 _lastChild = child; | |
312 if (_firstChild == null) | |
313 _firstChild = child; | |
314 } else { | |
315 assert(_firstChild != null); | |
316 assert(_lastChild != null); | |
317 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
318 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
319 assert(before.parentData is ParentDataType); | |
320 if (before.parentData.previousSibling == null) { | |
321 // insert at the start (_firstChild); we'll end up with two or more chil
dren | |
322 assert(before == _firstChild); | |
323 child.parentData.nextSibling = before; | |
324 before.parentData.previousSibling = child; | |
325 _firstChild = child; | |
326 } else { | |
327 // insert in the middle; we'll end up with three or more children | |
328 // set up links from child to siblings | |
329 child.parentData.previousSibling = before.parentData.previousSibling; | |
330 child.parentData.nextSibling = before; | |
331 // set up links from siblings to child | |
332 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
333 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
334 child.parentData.previousSibling.parentData.nextSibling = child; | |
335 child.parentData.nextSibling.parentData.previousSibling = child; | |
336 assert(before.parentData.previousSibling == child); | |
337 } | |
338 } | |
339 markNeedsLayout(); | |
340 } | |
341 void remove(ChildType child) { | |
342 assert(child.parentData is ParentDataType); | |
343 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
344 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
345 if (child.parentData.previousSibling == null) { | |
346 assert(_firstChild == child); | |
347 _firstChild = child.parentData.nextSibling; | |
348 } else { | |
349 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
350 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; | |
351 } | |
352 if (child.parentData.nextSibling == null) { | |
353 assert(_lastChild == child); | |
354 _lastChild = child.parentData.previousSibling; | |
355 } else { | |
356 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
357 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; | |
358 } | |
359 child.parentData.previousSibling = null; | |
360 child.parentData.nextSibling = null; | |
361 dropChild(child); | |
362 markNeedsLayout(); | |
363 } | |
364 void redepthChildren() { | |
365 ChildType child = _firstChild; | |
366 while (child != null) { | |
367 redepthChild(child); | |
368 assert(child.parentData is ParentDataType); | |
369 child = child.parentData.nextSibling; | |
370 } | |
371 } | |
372 void attachChildren() { | |
373 ChildType child = _firstChild; | |
374 while (child != null) { | |
375 child.attach(); | |
376 assert(child.parentData is ParentDataType); | |
377 child = child.parentData.nextSibling; | |
378 } | |
379 } | |
380 void detachChildren() { | |
381 ChildType child = _firstChild; | |
382 while (child != null) { | |
383 child.detach(); | |
384 assert(child.parentData is ParentDataType); | |
385 child = child.parentData.nextSibling; | |
386 } | |
387 } | |
388 | |
389 ChildType get firstChild => _firstChild; | |
390 ChildType get lastChild => _lastChild; | |
391 ChildType childAfter(ChildType child) { | |
392 assert(child.parentData is ParentDataType); | |
393 return child.parentData.nextSibling; | |
394 } | |
395 } | |
OLD | NEW |