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 'dart:math' as math; | |
6 import 'dart:sky' as sky; | |
7 import 'package:sky/framework/app.dart'; | |
8 import 'package:sky/framework/layout2.dart'; | |
9 | |
10 const double kTwoPi = 2 * math.PI; | |
11 | |
12 double deg(double radians) => radians * 180.0 / math.PI; | |
13 | |
14 class SectorConstraints { | |
15 const SectorConstraints({ | |
16 this.minDeltaRadius: 0.0, | |
17 this.maxDeltaRadius: double.INFINITY, | |
18 this.minDeltaTheta: 0.0, | |
19 this.maxDeltaTheta: kTwoPi | |
20 }); | |
21 | |
22 const SectorConstraints.tight({ double deltaRadius: 0.0, double deltaTheta: 0.
0 }) | |
23 : minDeltaRadius = deltaRadius, | |
24 maxDeltaRadius = deltaRadius, | |
25 minDeltaTheta = deltaTheta, | |
26 maxDeltaTheta = deltaTheta; | |
27 | |
28 final double minDeltaRadius; | |
29 final double maxDeltaRadius; | |
30 final double minDeltaTheta; | |
31 final double maxDeltaTheta; | |
32 | |
33 double constrainDeltaRadius(double deltaRadius) { | |
34 return clamp(min: minDeltaRadius, max: maxDeltaRadius, value: deltaRadius); | |
35 } | |
36 | |
37 double constrainDeltaTheta(double deltaTheta) { | |
38 return clamp(min: minDeltaTheta, max: maxDeltaTheta, value: deltaTheta); | |
39 } | |
40 } | |
41 | |
42 class SectorDimensions { | |
43 const SectorDimensions({ this.deltaRadius: 0.0, this.deltaTheta: 0.0 }); | |
44 | |
45 factory SectorDimensions.withConstraints( | |
46 SectorConstraints constraints, | |
47 { double deltaRadius: 0.0, double deltaTheta: 0.0 } | |
48 ) { | |
49 return new SectorDimensions( | |
50 deltaRadius: constraints.constrainDeltaRadius(deltaRadius), | |
51 deltaTheta: constraints.constrainDeltaTheta(deltaTheta) | |
52 ); | |
53 } | |
54 | |
55 final double deltaRadius; | |
56 final double deltaTheta; | |
57 } | |
58 | |
59 class SectorParentData extends ParentData { | |
60 double radius = 0.0; | |
61 double theta = 0.0; | |
62 } | |
63 | |
64 abstract class RenderSector extends RenderNode { | |
65 | |
66 void setParentData(RenderNode child) { | |
67 if (child.parentData is! SectorParentData) | |
68 child.parentData = new SectorParentData(); | |
69 } | |
70 | |
71 SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double
radius) { | |
72 return new SectorDimensions.withConstraints(constraints); | |
73 } | |
74 | |
75 SectorConstraints get constraints => super.constraints as SectorConstraints; | |
76 void performResize() { | |
77 // default behaviour for subclasses that have sizedByParent = true | |
78 deltaRadius = constraints.constrainDeltaRadius(0.0); | |
79 deltaTheta = constraints.constrainDeltaTheta(0.0); | |
80 } | |
81 void performLayout() { | |
82 // descendants have to either override performLayout() to set both | |
83 // the dimensions and lay out children, or, set sizedByParent to | |
84 // true so that performResize()'s logic above does its thing. | |
85 assert(sizedByParent); | |
86 } | |
87 | |
88 bool hitTest(HitTestResult result, { double radius, double theta }) { | |
89 assert(parentData is SectorParentData); | |
90 if (radius < parentData.radius || radius >= parentData.radius + deltaRadius
|| | |
91 theta < parentData.theta || theta >= parentData.theta + deltaTheta) | |
92 return false; | |
93 hitTestChildren(result, radius: radius, theta: theta); | |
94 result.add(this); | |
95 return true; | |
96 } | |
97 void hitTestChildren(HitTestResult result, { double radius, double theta }) {
} | |
98 | |
99 double deltaRadius; | |
100 double deltaTheta; | |
101 } | |
102 | |
103 class RenderDecoratedSector extends RenderSector { | |
104 | |
105 RenderDecoratedSector(BoxDecoration decoration) : _decoration = decoration; | |
106 | |
107 BoxDecoration _decoration; | |
108 BoxDecoration get decoration => _decoration; | |
109 void set decoration (BoxDecoration value) { | |
110 if (value == _decoration) | |
111 return; | |
112 _decoration = value; | |
113 markNeedsPaint(); | |
114 } | |
115 | |
116 // origin must be set to the center of the circle | |
117 void paint(RenderNodeDisplayList canvas) { | |
118 assert(deltaRadius != null); | |
119 assert(deltaTheta != null); | |
120 assert(parentData is SectorParentData); | |
121 | |
122 if (_decoration == null) | |
123 return; | |
124 | |
125 if (_decoration.backgroundColor != null) { | |
126 sky.Paint paint = new sky.Paint()..color = _decoration.backgroundColor; | |
127 sky.Path path = new sky.Path(); | |
128 double outerRadius = (parentData.radius + deltaRadius); | |
129 sky.Rect outerBounds = new sky.Rect.fromLTRB(-outerRadius, -outerRadius, o
uterRadius, outerRadius); | |
130 path.arcTo(outerBounds, deg(parentData.theta), deg(deltaTheta), true); | |
131 double innerRadius = parentData.radius; | |
132 sky.Rect innerBounds = new sky.Rect.fromLTRB(-innerRadius, -innerRadius, i
nnerRadius, innerRadius); | |
133 path.arcTo(innerBounds, deg(parentData.theta + deltaTheta), deg(-deltaThet
a), false); | |
134 path.close(); | |
135 canvas.drawPath(path, paint); | |
136 } | |
137 } | |
138 } | |
139 | |
140 class SectorChildListParentData extends SectorParentData with ContainerParentDat
aMixin<RenderSector> { } | |
141 | |
142 class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRende
rNodeMixin<RenderSector, SectorChildListParentData> { | |
143 RenderSectorWithChildren(BoxDecoration decoration) : super(decoration); | |
144 | |
145 void hitTestChildren(HitTestResult result, { double radius, double theta }) { | |
146 RenderSector child = lastChild; | |
147 while (child != null) { | |
148 assert(child.parentData is SectorChildListParentData); | |
149 if (child.hitTest(result, radius: radius, theta: theta)) | |
150 return; | |
151 child = child.parentData.previousSibling; | |
152 } | |
153 } | |
154 } | |
155 | |
156 class RenderSectorRing extends RenderSectorWithChildren { | |
157 // lays out RenderSector children in a ring | |
158 | |
159 RenderSectorRing({ | |
160 BoxDecoration decoration, | |
161 double deltaRadius: double.INFINITY, | |
162 double padding: 0.0 | |
163 }) : super(decoration), _padding = padding, _desiredDeltaRadius = deltaRadius; | |
164 | |
165 double _desiredDeltaRadius; | |
166 double get desiredDeltaRadius => _desiredDeltaRadius; | |
167 void set desiredDeltaRadius(double value) { | |
168 assert(value != null); | |
169 if (_desiredDeltaRadius != value) { | |
170 _desiredDeltaRadius = value; | |
171 markNeedsLayout(); | |
172 } | |
173 } | |
174 | |
175 double _padding; | |
176 double get padding => _padding; | |
177 void set padding(double value) { | |
178 // TODO(ianh): avoid code duplication | |
179 assert(value != null); | |
180 if (_padding != value) { | |
181 _padding = value; | |
182 markNeedsLayout(); | |
183 } | |
184 } | |
185 | |
186 void setParentData(RenderNode child) { | |
187 // TODO(ianh): avoid code duplication | |
188 if (child.parentData is! SectorChildListParentData) | |
189 child.parentData = new SectorChildListParentData(); | |
190 } | |
191 | |
192 SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double
radius) { | |
193 double outerDeltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadiu
s); | |
194 double innerDeltaRadius = outerDeltaRadius - padding * 2.0; | |
195 double childRadius = radius + padding; | |
196 double paddingTheta = math.atan(padding / (radius + outerDeltaRadius)); | |
197 double innerTheta = paddingTheta; // increments with each child | |
198 double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddi
ngTheta); | |
199 RenderSector child = firstChild; | |
200 while (child != null) { | |
201 SectorConstraints innerConstraints = new SectorConstraints( | |
202 maxDeltaRadius: innerDeltaRadius, | |
203 maxDeltaTheta: remainingDeltaTheta | |
204 ); | |
205 SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConst
raints, childRadius); | |
206 innerTheta += childDimensions.deltaTheta; | |
207 remainingDeltaTheta -= childDimensions.deltaTheta; | |
208 assert(child.parentData is SectorChildListParentData); | |
209 child = child.parentData.nextSibling; | |
210 if (child != null) { | |
211 innerTheta += paddingTheta; | |
212 remainingDeltaTheta -= paddingTheta; | |
213 } | |
214 } | |
215 return new SectorDimensions.withConstraints(constraints, | |
216 deltaRadius: outerDeltaRadius, | |
217 deltaTheta: innerTheta); | |
218 } | |
219 | |
220 void performLayout() { | |
221 assert(this.parentData is SectorParentData); | |
222 deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius); | |
223 assert(deltaRadius < double.INFINITY); | |
224 double innerDeltaRadius = deltaRadius - padding * 2.0; | |
225 double childRadius = this.parentData.radius + padding; | |
226 double paddingTheta = math.atan(padding / (this.parentData.radius + deltaRad
ius)); | |
227 double innerTheta = paddingTheta; // increments with each child | |
228 double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddi
ngTheta); | |
229 RenderSector child = firstChild; | |
230 while (child != null) { | |
231 SectorConstraints innerConstraints = new SectorConstraints( | |
232 maxDeltaRadius: innerDeltaRadius, | |
233 maxDeltaTheta: remainingDeltaTheta | |
234 ); | |
235 assert(child.parentData is SectorParentData); | |
236 child.parentData.theta = innerTheta; | |
237 child.parentData.radius = childRadius; | |
238 child.layout(innerConstraints, parentUsesSize: true); | |
239 innerTheta += child.deltaTheta; | |
240 remainingDeltaTheta -= child.deltaTheta; | |
241 assert(child.parentData is SectorChildListParentData); | |
242 child = child.parentData.nextSibling; | |
243 if (child != null) { | |
244 innerTheta += paddingTheta; | |
245 remainingDeltaTheta -= paddingTheta; | |
246 } | |
247 } | |
248 deltaTheta = innerTheta; | |
249 } | |
250 | |
251 // paint origin is 0,0 of our circle | |
252 // each sector then knows how to paint itself at its location | |
253 void paint(RenderNodeDisplayList canvas) { | |
254 // TODO(ianh): avoid code duplication | |
255 super.paint(canvas); | |
256 RenderSector child = firstChild; | |
257 while (child != null) { | |
258 assert(child.parentData is SectorChildListParentData); | |
259 canvas.paintChild(child, new sky.Point(0.0, 0.0)); | |
260 child = child.parentData.nextSibling; | |
261 } | |
262 } | |
263 | |
264 } | |
265 | |
266 class RenderSectorSlice extends RenderSectorWithChildren { | |
267 // lays out RenderSector children in a stack | |
268 | |
269 RenderSectorSlice({ | |
270 BoxDecoration decoration, | |
271 double deltaTheta: kTwoPi, | |
272 double padding: 0.0 | |
273 }) : super(decoration), _padding = padding, _desiredDeltaTheta = deltaTheta; | |
274 | |
275 double _desiredDeltaTheta; | |
276 double get desiredDeltaTheta => _desiredDeltaTheta; | |
277 void set desiredDeltaTheta(double value) { | |
278 assert(value != null); | |
279 if (_desiredDeltaTheta != value) { | |
280 _desiredDeltaTheta = value; | |
281 markNeedsLayout(); | |
282 } | |
283 } | |
284 | |
285 double _padding; | |
286 double get padding => _padding; | |
287 void set padding(double value) { | |
288 // TODO(ianh): avoid code duplication | |
289 assert(value != null); | |
290 if (_padding != value) { | |
291 _padding = value; | |
292 markNeedsLayout(); | |
293 } | |
294 } | |
295 | |
296 void setParentData(RenderNode child) { | |
297 // TODO(ianh): avoid code duplication | |
298 if (child.parentData is! SectorChildListParentData) | |
299 child.parentData = new SectorChildListParentData(); | |
300 } | |
301 | |
302 SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double
radius) { | |
303 assert(this.parentData is SectorParentData); | |
304 double paddingTheta = math.atan(padding / this.parentData.radius); | |
305 double outerDeltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta); | |
306 double innerDeltaTheta = outerDeltaTheta - paddingTheta * 2.0; | |
307 double childRadius = this.parentData.radius + padding; | |
308 double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0); | |
309 RenderSector child = firstChild; | |
310 while (child != null) { | |
311 SectorConstraints innerConstraints = new SectorConstraints( | |
312 maxDeltaRadius: remainingDeltaRadius, | |
313 maxDeltaTheta: innerDeltaTheta | |
314 ); | |
315 SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConst
raints, childRadius); | |
316 childRadius += childDimensions.deltaRadius; | |
317 remainingDeltaRadius -= childDimensions.deltaRadius; | |
318 assert(child.parentData is SectorChildListParentData); | |
319 child = child.parentData.nextSibling; | |
320 childRadius += padding; | |
321 remainingDeltaRadius -= padding; | |
322 } | |
323 return new SectorDimensions.withConstraints(constraints, | |
324 deltaRadius: childRadius - this.
parentData.radius, | |
325 deltaTheta: outerDeltaTheta); | |
326 } | |
327 | |
328 void performLayout() { | |
329 assert(this.parentData is SectorParentData); | |
330 deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta); | |
331 assert(deltaTheta <= kTwoPi); | |
332 double paddingTheta = math.atan(padding / this.parentData.radius); | |
333 double innerTheta = this.parentData.theta + paddingTheta; | |
334 double innerDeltaTheta = deltaTheta - paddingTheta * 2.0; | |
335 double childRadius = this.parentData.radius + padding; | |
336 double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0); | |
337 RenderSector child = firstChild; | |
338 while (child != null) { | |
339 SectorConstraints innerConstraints = new SectorConstraints( | |
340 maxDeltaRadius: remainingDeltaRadius, | |
341 maxDeltaTheta: innerDeltaTheta | |
342 ); | |
343 child.parentData.theta = innerTheta; | |
344 child.parentData.radius = childRadius; | |
345 child.layout(innerConstraints, parentUsesSize: true); | |
346 childRadius += child.deltaRadius; | |
347 remainingDeltaRadius -= child.deltaRadius; | |
348 assert(child.parentData is SectorChildListParentData); | |
349 child = child.parentData.nextSibling; | |
350 childRadius += padding; | |
351 remainingDeltaRadius -= padding; | |
352 } | |
353 deltaRadius = childRadius - this.parentData.radius; | |
354 } | |
355 | |
356 // paint origin is 0,0 of our circle | |
357 // each sector then knows how to paint itself at its location | |
358 void paint(RenderNodeDisplayList canvas) { | |
359 // TODO(ianh): avoid code duplication | |
360 super.paint(canvas); | |
361 RenderSector child = firstChild; | |
362 while (child != null) { | |
363 assert(child.parentData is SectorChildListParentData); | |
364 canvas.paintChild(child, new sky.Point(0.0, 0.0)); | |
365 child = child.parentData.nextSibling; | |
366 } | |
367 } | |
368 | |
369 } | |
370 | |
371 class RenderBoxToRenderSectorAdapter extends RenderBox { | |
372 | |
373 RenderBoxToRenderSectorAdapter({ double innerRadius: 0.0, RenderSector child }
) : | |
374 _innerRadius = innerRadius { | |
375 _child = child; | |
376 adoptChild(_child); | |
377 } | |
378 | |
379 double _innerRadius; | |
380 double get innerRadius => _innerRadius; | |
381 void set innerRadius(double value) { | |
382 _innerRadius = value; | |
383 markNeedsLayout(); | |
384 } | |
385 | |
386 RenderSector _child; | |
387 RenderSector get child => _child; | |
388 void set child(RenderSector value) { | |
389 if (_child != null) | |
390 dropChild(_child); | |
391 _child = value; | |
392 adoptChild(_child); | |
393 markNeedsLayout(); | |
394 } | |
395 | |
396 void setParentData(RenderNode child) { | |
397 if (child.parentData is! SectorParentData) | |
398 child.parentData = new SectorParentData(); | |
399 } | |
400 | |
401 sky.Size getIntrinsicDimensions(BoxConstraints constraints) { | |
402 if (child == null) | |
403 return constraints.constrain(new sky.Size(0.0, 0.0)); | |
404 assert(child is RenderSector); | |
405 assert(child.parentData is SectorParentData); | |
406 assert(!constraints.isInfinite); | |
407 double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxH
eight) / 2.0 - innerRadius; | |
408 SectorDimensions childDimensions = child.getIntrinsicDimensions(new SectorCo
nstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius); | |
409 double dimension = (innerRadius + childDimensions.deltaRadius) * 2.0; | |
410 return constraints.constrain(new sky.Size(dimension, dimension)); | |
411 } | |
412 | |
413 void performLayout() { | |
414 if (child == null) { | |
415 size = constraints.constrain(new sky.Size(0.0, 0.0)); | |
416 } else { | |
417 assert(child is RenderSector); | |
418 assert(!constraints.isInfinite); | |
419 print("constraint maxes: ${constraints.maxWidth} and ${constraints.maxHeig
ht}"); | |
420 double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.ma
xHeight) / 2.0 - innerRadius; | |
421 print("maxChildDeltaRadius = $maxChildDeltaRadius"); | |
422 assert(child.parentData is SectorParentData); | |
423 child.parentData.radius = innerRadius; | |
424 child.parentData.theta = 0.0; | |
425 child.layout(new SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), p
arentUsesSize: true); | |
426 double dimension = (innerRadius + child.deltaRadius) * 2.0; | |
427 size = constraints.constrain(new sky.Size(dimension, dimension)); | |
428 } | |
429 } | |
430 | |
431 // paint origin is 0,0 of our circle | |
432 void paint(RenderNodeDisplayList canvas) { | |
433 super.paint(canvas); | |
434 if (child != null) { | |
435 sky.Rect bounds = new sky.Rect.fromSize(size); | |
436 canvas.paintChild(child, bounds.center); | |
437 } | |
438 } | |
439 | |
440 bool hitTest(HitTestResult result, { sky.Point position }) { | |
441 double x = position.x; | |
442 double y = position.y; | |
443 if (child == null) | |
444 return false; | |
445 // translate to our origin | |
446 x -= size.width/2.0; | |
447 y -= size.height/2.0; | |
448 // convert to radius/theta | |
449 double radius = math.sqrt(x*x+y*y); | |
450 double theta = (math.atan2(x, -y) - math.PI/2.0) % kTwoPi; | |
451 if (radius < innerRadius) | |
452 return false; | |
453 if (radius >= innerRadius + child.deltaRadius) | |
454 return false; | |
455 if (theta > child.deltaTheta) | |
456 return false; | |
457 child.hitTest(result, radius: radius, theta: theta); | |
458 result.add(this); | |
459 return true; | |
460 } | |
461 | |
462 } | |
463 | |
464 class RenderSolidColor extends RenderDecoratedSector { | |
465 RenderSolidColor(int backgroundColor, { | |
466 this.desiredDeltaRadius: double.INFINITY, | |
467 this.desiredDeltaTheta: kTwoPi | |
468 }) : this.backgroundColor = backgroundColor, | |
469 super(new BoxDecoration(backgroundColor: backgroundColor)); | |
470 | |
471 double desiredDeltaRadius; | |
472 double desiredDeltaTheta; | |
473 final int backgroundColor; | |
474 | |
475 SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double
radius) { | |
476 return new SectorDimensions.withConstraints(constraints, deltaTheta: 1.0); /
/ 1.0 radians | |
477 } | |
478 | |
479 void performLayout() { | |
480 deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius); | |
481 deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta); | |
482 } | |
483 | |
484 void handlePointer(sky.PointerEvent event) { | |
485 if (event.type == 'pointerdown') | |
486 decoration = new BoxDecoration(backgroundColor: 0xFFFF0000); | |
487 else if (event.type == 'pointerup') | |
488 decoration = new BoxDecoration(backgroundColor: backgroundColor); | |
489 } | |
490 } | |
491 | |
492 AppView app; | |
493 | |
494 void main() { | |
495 | |
496 var rootCircle = new RenderSectorRing(padding: 20.0); | |
497 rootCircle.add(new RenderSolidColor(0xFF00FFFF, desiredDeltaTheta: kTwoPi * 0.
15)); | |
498 rootCircle.add(new RenderSolidColor(0xFF0000FF, desiredDeltaTheta: kTwoPi * 0.
4)); | |
499 var stack = new RenderSectorSlice(padding: 2.0); | |
500 stack.add(new RenderSolidColor(0xFFFFFF00, desiredDeltaRadius: 20.0)); | |
501 stack.add(new RenderSolidColor(0xFFFF9000, desiredDeltaRadius: 20.0)); | |
502 stack.add(new RenderSolidColor(0xFF00FF00)); | |
503 rootCircle.add(stack); | |
504 | |
505 var root = new RenderBoxToRenderSectorAdapter(innerRadius: 50.0, child: rootCi
rcle); | |
506 app = new AppView(root); | |
507 } | |
OLD | NEW |