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