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