sector_layout.dart 22.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hixie's avatar
Hixie committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

Hixie's avatar
Hixie committed
7 8 9
import 'dart:math' as math;

import 'package:flutter/rendering.dart';
10
import 'package:flutter/gestures.dart';
11
import 'package:meta/meta.dart';
Hixie's avatar
Hixie committed
12

13
const double kTwoPi = 2 * math.pi;
Hixie's avatar
Hixie committed
14 15 16

class SectorConstraints extends Constraints {
  const SectorConstraints({
17 18 19
    this.minDeltaRadius = 0.0,
    this.maxDeltaRadius = double.infinity,
    this.minDeltaTheta = 0.0,
20
    this.maxDeltaTheta = kTwoPi,
21 22
  }) : assert(maxDeltaRadius >= minDeltaRadius),
       assert(maxDeltaTheta >= minDeltaTheta);
Hixie's avatar
Hixie committed
23

24
  const SectorConstraints.tight({ double deltaRadius = 0.0, double deltaTheta = 0.0 })
Hixie's avatar
Hixie committed
25 26 27 28 29 30 31 32 33 34 35
    : minDeltaRadius = deltaRadius,
      maxDeltaRadius = deltaRadius,
      minDeltaTheta = deltaTheta,
      maxDeltaTheta = deltaTheta;

  final double minDeltaRadius;
  final double maxDeltaRadius;
  final double minDeltaTheta;
  final double maxDeltaTheta;

  double constrainDeltaRadius(double deltaRadius) {
36
    return deltaRadius.clamp(minDeltaRadius, maxDeltaRadius) as double;
Hixie's avatar
Hixie committed
37 38 39
  }

  double constrainDeltaTheta(double deltaTheta) {
40
    return deltaTheta.clamp(minDeltaTheta, maxDeltaTheta) as double;
Hixie's avatar
Hixie committed
41 42
  }

43
  @override
Hixie's avatar
Hixie committed
44
  bool get isTight => minDeltaTheta >= maxDeltaTheta && minDeltaTheta >= maxDeltaTheta;
45

46
  @override
47
  bool get isNormalized => minDeltaRadius <= maxDeltaRadius && minDeltaTheta <= maxDeltaTheta;
48 49

  @override
50
  bool debugAssertIsValid({
51
    bool isAppliedConstraint = false,
52
    InformationCollector informationCollector,
53
  }) {
54 55 56
    assert(isNormalized);
    return isNormalized;
  }
Hixie's avatar
Hixie committed
57 58 59
}

class SectorDimensions {
60
  const SectorDimensions({ this.deltaRadius = 0.0, this.deltaTheta = 0.0 });
Hixie's avatar
Hixie committed
61 62

  factory SectorDimensions.withConstraints(
63 64 65 66
    SectorConstraints constraints, {
    double deltaRadius = 0.0,
    double deltaTheta = 0.0,
  }) {
67
    return SectorDimensions(
Hixie's avatar
Hixie committed
68
      deltaRadius: constraints.constrainDeltaRadius(deltaRadius),
69
      deltaTheta: constraints.constrainDeltaTheta(deltaTheta),
Hixie's avatar
Hixie committed
70 71 72 73 74 75 76 77 78 79 80 81
    );
  }

  final double deltaRadius;
  final double deltaTheta;
}

class SectorParentData extends ParentData {
  double radius = 0.0;
  double theta = 0.0;
}

82 83 84 85 86 87 88 89 90 91 92
/// Base class for [RenderObject]s that live in a polar coordinate space.
///
/// In a polar coordinate system each point on a plane is determined by a
/// distance from a reference point ("radius") and an angle from a reference
/// direction ("theta").
///
/// See also:
///
///  * <https://en.wikipedia.org/wiki/Polar_coordinate_system>, which defines
///    the polar coordinate space.
///  * [RenderBox], which is the base class for [RenderObject]s that live in a
93
///    Cartesian coordinate space.
Hixie's avatar
Hixie committed
94 95
abstract class RenderSector extends RenderObject {

96
  @override
Hixie's avatar
Hixie committed
97 98
  void setupParentData(RenderObject child) {
    if (child.parentData is! SectorParentData)
99
      child.parentData = SectorParentData();
Hixie's avatar
Hixie committed
100 101 102 103
  }

  // RenderSectors always use SectorParentData subclasses, as they need to be
  // able to read their position information for painting and hit testing.
104
  @override
105
  SectorParentData get parentData => super.parentData as SectorParentData;
Hixie's avatar
Hixie committed
106 107

  SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
108
    return SectorDimensions.withConstraints(constraints);
Hixie's avatar
Hixie committed
109 110
  }

111
  @override
112
  SectorConstraints get constraints => super.constraints as SectorConstraints;
113 114

  @override
115
  void debugAssertDoesMeetConstraints() {
Hixie's avatar
Hixie committed
116 117
    assert(constraints != null);
    assert(deltaRadius != null);
118
    assert(deltaRadius < double.infinity);
Hixie's avatar
Hixie committed
119
    assert(deltaTheta != null);
120
    assert(deltaTheta < double.infinity);
121 122 123 124
    assert(constraints.minDeltaRadius <= deltaRadius);
    assert(deltaRadius <= math.max(constraints.minDeltaRadius, constraints.maxDeltaRadius));
    assert(constraints.minDeltaTheta <= deltaTheta);
    assert(deltaTheta <= math.max(constraints.minDeltaTheta, constraints.maxDeltaTheta));
Hixie's avatar
Hixie committed
125
  }
126 127

  @override
Hixie's avatar
Hixie committed
128
  void performResize() {
129
    // default behavior for subclasses that have sizedByParent = true
Hixie's avatar
Hixie committed
130 131 132
    deltaRadius = constraints.constrainDeltaRadius(0.0);
    deltaTheta = constraints.constrainDeltaTheta(0.0);
  }
133 134

  @override
Hixie's avatar
Hixie committed
135 136 137 138 139 140 141
  void performLayout() {
    // descendants have to either override performLayout() to set both
    // the dimensions and lay out children, or, set sizedByParent to
    // true so that performResize()'s logic above does its thing.
    assert(sizedByParent);
  }

142
  @override
143
  Rect get paintBounds => Rect.fromLTWH(0.0, 0.0, 2.0 * deltaRadius, 2.0 * deltaRadius);
144 145

  @override
146
  Rect get semanticBounds => Rect.fromLTWH(-deltaRadius, -deltaRadius, 2.0 * deltaRadius, 2.0 * deltaRadius);
Hixie's avatar
Hixie committed
147

148
  bool hitTest(SectorHitTestResult result, { double radius, double theta }) {
Hixie's avatar
Hixie committed
149 150 151 152
    if (radius < parentData.radius || radius >= parentData.radius + deltaRadius ||
        theta < parentData.theta || theta >= parentData.theta + deltaTheta)
      return false;
    hitTestChildren(result, radius: radius, theta: theta);
153
    result.add(SectorHitTestEntry(this, radius: radius, theta: theta));
Hixie's avatar
Hixie committed
154 155
    return true;
  }
156
  void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) { }
Hixie's avatar
Hixie committed
157 158 159 160 161 162 163 164 165 166 167

  double deltaRadius;
  double deltaTheta;
}

abstract class RenderDecoratedSector extends RenderSector {

  RenderDecoratedSector(BoxDecoration decoration) : _decoration = decoration;

  BoxDecoration _decoration;
  BoxDecoration get decoration => _decoration;
168
  set decoration(BoxDecoration value) {
Hixie's avatar
Hixie committed
169 170 171 172 173 174 175
    if (value == _decoration)
      return;
    _decoration = value;
    markNeedsPaint();
  }

  // offset must point to the center of the circle
176
  @override
Hixie's avatar
Hixie committed
177 178 179 180 181 182 183 184
  void paint(PaintingContext context, Offset offset) {
    assert(deltaRadius != null);
    assert(deltaTheta != null);
    assert(parentData is SectorParentData);

    if (_decoration == null)
      return;

185
    if (_decoration.color != null) {
Adam Barth's avatar
Adam Barth committed
186
      final Canvas canvas = context.canvas;
187 188
      final Paint paint = Paint()..color = _decoration.color;
      final Path path = Path();
189
      final double outerRadius = parentData.radius + deltaRadius;
190
      final Rect outerBounds = Rect.fromLTRB(offset.dx-outerRadius, offset.dy-outerRadius, offset.dx+outerRadius, offset.dy+outerRadius);
Hixie's avatar
Hixie committed
191
      path.arcTo(outerBounds, parentData.theta, deltaTheta, true);
192
      final double innerRadius = parentData.radius;
193
      final Rect innerBounds = Rect.fromLTRB(offset.dx-innerRadius, offset.dy-innerRadius, offset.dx+innerRadius, offset.dy+innerRadius);
Hixie's avatar
Hixie committed
194 195 196 197 198 199 200 201 202 203 204 205 206
      path.arcTo(innerBounds, parentData.theta + deltaTheta, -deltaTheta, false);
      path.close();
      canvas.drawPath(path, paint);
    }
  }

}

class SectorChildListParentData extends SectorParentData with ContainerParentDataMixin<RenderSector> { }

class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRenderObjectMixin<RenderSector, SectorChildListParentData> {
  RenderSectorWithChildren(BoxDecoration decoration) : super(decoration);

207
  @override
208
  void hitTestChildren(SectorHitTestResult result, { double radius, double theta }) {
Hixie's avatar
Hixie committed
209 210 211 212
    RenderSector child = lastChild;
    while (child != null) {
      if (child.hitTest(result, radius: radius, theta: theta))
        return;
213
      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
Hixie's avatar
Hixie committed
214 215 216 217
      child = childParentData.previousSibling;
    }
  }

218
  @override
Hixie's avatar
Hixie committed
219 220 221 222
  void visitChildren(RenderObjectVisitor visitor) {
    RenderSector child = lastChild;
    while (child != null) {
      visitor(child);
223
      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
Hixie's avatar
Hixie committed
224 225 226 227 228 229 230 231 232 233
      child = childParentData.previousSibling;
    }
  }
}

class RenderSectorRing extends RenderSectorWithChildren {
  // lays out RenderSector children in a ring

  RenderSectorRing({
    BoxDecoration decoration,
234
    double deltaRadius = double.infinity,
235
    double padding = 0.0,
236 237 238 239
  }) : _padding = padding,
       assert(deltaRadius >= 0.0),
       _desiredDeltaRadius = deltaRadius,
       super(decoration);
Hixie's avatar
Hixie committed
240 241 242

  double _desiredDeltaRadius;
  double get desiredDeltaRadius => _desiredDeltaRadius;
243
  set desiredDeltaRadius(double value) {
Hixie's avatar
Hixie committed
244
    assert(value != null);
245
    assert(value >= 0);
Hixie's avatar
Hixie committed
246 247 248 249 250 251 252 253
    if (_desiredDeltaRadius != value) {
      _desiredDeltaRadius = value;
      markNeedsLayout();
    }
  }

  double _padding;
  double get padding => _padding;
254
  set padding(double value) {
Hixie's avatar
Hixie committed
255 256 257 258 259 260 261 262
    // TODO(ianh): avoid code duplication
    assert(value != null);
    if (_padding != value) {
      _padding = value;
      markNeedsLayout();
    }
  }

263
  @override
Hixie's avatar
Hixie committed
264 265 266
  void setupParentData(RenderObject child) {
    // TODO(ianh): avoid code duplication
    if (child.parentData is! SectorChildListParentData)
267
      child.parentData = SectorChildListParentData();
Hixie's avatar
Hixie committed
268 269
  }

270
  @override
Hixie's avatar
Hixie committed
271
  SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
272
    final double outerDeltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
273
    final double innerDeltaRadius = math.max(0.0, outerDeltaRadius - padding * 2.0);
274 275
    final double childRadius = radius + padding;
    final double paddingTheta = math.atan(padding / (radius + outerDeltaRadius));
Hixie's avatar
Hixie committed
276
    double innerTheta = paddingTheta; // increments with each child
277
    double remainingDeltaTheta = math.max(0.0, constraints.maxDeltaTheta - (innerTheta + paddingTheta));
Hixie's avatar
Hixie committed
278 279
    RenderSector child = firstChild;
    while (child != null) {
280
      final SectorConstraints innerConstraints = SectorConstraints(
Hixie's avatar
Hixie committed
281
        maxDeltaRadius: innerDeltaRadius,
282
        maxDeltaTheta: remainingDeltaTheta,
Hixie's avatar
Hixie committed
283
      );
284
      final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
Hixie's avatar
Hixie committed
285 286
      innerTheta += childDimensions.deltaTheta;
      remainingDeltaTheta -= childDimensions.deltaTheta;
287
      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
Hixie's avatar
Hixie committed
288 289 290 291 292 293
      child = childParentData.nextSibling;
      if (child != null) {
        innerTheta += paddingTheta;
        remainingDeltaTheta -= paddingTheta;
      }
    }
294
    return SectorDimensions.withConstraints(constraints,
Hixie's avatar
Hixie committed
295 296 297 298
                                                deltaRadius: outerDeltaRadius,
                                                deltaTheta: innerTheta);
  }

299
  @override
Hixie's avatar
Hixie committed
300
  void performLayout() {
301
    assert(parentData is SectorParentData);
Hixie's avatar
Hixie committed
302
    deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
303
    assert(deltaRadius < double.infinity);
304
    final double innerDeltaRadius = deltaRadius - padding * 2.0;
305 306
    final double childRadius = parentData.radius + padding;
    final double paddingTheta = math.atan(padding / (parentData.radius + deltaRadius));
Hixie's avatar
Hixie committed
307 308 309 310
    double innerTheta = paddingTheta; // increments with each child
    double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta);
    RenderSector child = firstChild;
    while (child != null) {
311
      final SectorConstraints innerConstraints = SectorConstraints(
Hixie's avatar
Hixie committed
312
        maxDeltaRadius: innerDeltaRadius,
313
        maxDeltaTheta: remainingDeltaTheta,
Hixie's avatar
Hixie committed
314 315 316 317 318 319 320
      );
      assert(child.parentData is SectorParentData);
      child.parentData.theta = innerTheta;
      child.parentData.radius = childRadius;
      child.layout(innerConstraints, parentUsesSize: true);
      innerTheta += child.deltaTheta;
      remainingDeltaTheta -= child.deltaTheta;
321
      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
Hixie's avatar
Hixie committed
322 323 324 325 326 327 328 329 330 331 332
      child = childParentData.nextSibling;
      if (child != null) {
        innerTheta += paddingTheta;
        remainingDeltaTheta -= paddingTheta;
      }
    }
    deltaTheta = innerTheta;
  }

  // offset must point to the center of our circle
  // each sector then knows how to paint itself at its location
333
  @override
Hixie's avatar
Hixie committed
334 335 336 337 338
  void paint(PaintingContext context, Offset offset) {
    // TODO(ianh): avoid code duplication
    super.paint(context, offset);
    RenderSector child = firstChild;
    while (child != null) {
Adam Barth's avatar
Adam Barth committed
339
      context.paintChild(child, offset);
340
      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
Hixie's avatar
Hixie committed
341 342 343 344 345 346 347 348 349 350 351
      child = childParentData.nextSibling;
    }
  }

}

class RenderSectorSlice extends RenderSectorWithChildren {
  // lays out RenderSector children in a stack

  RenderSectorSlice({
    BoxDecoration decoration,
352
    double deltaTheta = kTwoPi,
353
    double padding = 0.0,
Hixie's avatar
Hixie committed
354 355 356 357
  }) : _padding = padding, _desiredDeltaTheta = deltaTheta, super(decoration);

  double _desiredDeltaTheta;
  double get desiredDeltaTheta => _desiredDeltaTheta;
358
  set desiredDeltaTheta(double value) {
Hixie's avatar
Hixie committed
359 360 361 362 363 364 365 366 367
    assert(value != null);
    if (_desiredDeltaTheta != value) {
      _desiredDeltaTheta = value;
      markNeedsLayout();
    }
  }

  double _padding;
  double get padding => _padding;
368
  set padding(double value) {
Hixie's avatar
Hixie committed
369 370 371 372 373 374 375 376
    // TODO(ianh): avoid code duplication
    assert(value != null);
    if (_padding != value) {
      _padding = value;
      markNeedsLayout();
    }
  }

377
  @override
Hixie's avatar
Hixie committed
378 379 380
  void setupParentData(RenderObject child) {
    // TODO(ianh): avoid code duplication
    if (child.parentData is! SectorChildListParentData)
381
      child.parentData = SectorChildListParentData();
Hixie's avatar
Hixie committed
382 383
  }

384
  @override
Hixie's avatar
Hixie committed
385
  SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
386 387
    assert(parentData is SectorParentData);
    final double paddingTheta = math.atan(padding / parentData.radius);
388 389
    final double outerDeltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
    final double innerDeltaTheta = outerDeltaTheta - paddingTheta * 2.0;
390
    double childRadius = parentData.radius + padding;
Hixie's avatar
Hixie committed
391 392 393
    double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
    RenderSector child = firstChild;
    while (child != null) {
394
      final SectorConstraints innerConstraints = SectorConstraints(
Hixie's avatar
Hixie committed
395
        maxDeltaRadius: remainingDeltaRadius,
396
        maxDeltaTheta: innerDeltaTheta,
Hixie's avatar
Hixie committed
397
      );
398
      final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
Hixie's avatar
Hixie committed
399 400
      childRadius += childDimensions.deltaRadius;
      remainingDeltaRadius -= childDimensions.deltaRadius;
401
      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
Hixie's avatar
Hixie committed
402 403 404 405
      child = childParentData.nextSibling;
      childRadius += padding;
      remainingDeltaRadius -= padding;
    }
406
    return SectorDimensions.withConstraints(constraints,
407
                                                deltaRadius: childRadius - parentData.radius,
Hixie's avatar
Hixie committed
408 409 410
                                                deltaTheta: outerDeltaTheta);
  }

411
  @override
Hixie's avatar
Hixie committed
412
  void performLayout() {
413
    assert(parentData is SectorParentData);
Hixie's avatar
Hixie committed
414 415
    deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
    assert(deltaTheta <= kTwoPi);
416 417
    final double paddingTheta = math.atan(padding / parentData.radius);
    final double innerTheta = parentData.theta + paddingTheta;
418
    final double innerDeltaTheta = deltaTheta - paddingTheta * 2.0;
419
    double childRadius = parentData.radius + padding;
Hixie's avatar
Hixie committed
420 421 422
    double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
    RenderSector child = firstChild;
    while (child != null) {
423
      final SectorConstraints innerConstraints = SectorConstraints(
Hixie's avatar
Hixie committed
424
        maxDeltaRadius: remainingDeltaRadius,
425
        maxDeltaTheta: innerDeltaTheta,
Hixie's avatar
Hixie committed
426 427 428 429 430 431
      );
      child.parentData.theta = innerTheta;
      child.parentData.radius = childRadius;
      child.layout(innerConstraints, parentUsesSize: true);
      childRadius += child.deltaRadius;
      remainingDeltaRadius -= child.deltaRadius;
432
      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
Hixie's avatar
Hixie committed
433 434 435 436
      child = childParentData.nextSibling;
      childRadius += padding;
      remainingDeltaRadius -= padding;
    }
437
    deltaRadius = childRadius - parentData.radius;
Hixie's avatar
Hixie committed
438 439 440 441
  }

  // offset must point to the center of our circle
  // each sector then knows how to paint itself at its location
442
  @override
Hixie's avatar
Hixie committed
443 444 445 446 447 448
  void paint(PaintingContext context, Offset offset) {
    // TODO(ianh): avoid code duplication
    super.paint(context, offset);
    RenderSector child = firstChild;
    while (child != null) {
      assert(child.parentData is SectorChildListParentData);
Adam Barth's avatar
Adam Barth committed
449
      context.paintChild(child, offset);
450
      final SectorChildListParentData childParentData = child.parentData as SectorChildListParentData;
Hixie's avatar
Hixie committed
451 452 453 454 455 456 457 458
      child = childParentData.nextSibling;
    }
  }

}

class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChildMixin<RenderSector> {

459 460
  RenderBoxToRenderSectorAdapter({ double innerRadius = 0.0, RenderSector child })
    : _innerRadius = innerRadius {
Hixie's avatar
Hixie committed
461 462 463 464 465
    this.child = child;
  }

  double _innerRadius;
  double get innerRadius => _innerRadius;
466
  set innerRadius(double value) {
Hixie's avatar
Hixie committed
467 468 469 470
    _innerRadius = value;
    markNeedsLayout();
  }

471
  @override
Hixie's avatar
Hixie committed
472 473
  void setupParentData(RenderObject child) {
    if (child.parentData is! SectorParentData)
474
      child.parentData = SectorParentData();
Hixie's avatar
Hixie committed
475 476
  }

477
  @override
478
  double computeMinIntrinsicWidth(double height) {
Hixie's avatar
Hixie committed
479
    if (child == null)
480 481
      return 0.0;
    return getIntrinsicDimensions(height: height).width;
Hixie's avatar
Hixie committed
482 483
  }

484
  @override
485
  double computeMaxIntrinsicWidth(double height) {
Hixie's avatar
Hixie committed
486
    if (child == null)
487 488
      return 0.0;
    return getIntrinsicDimensions(height: height).width;
Hixie's avatar
Hixie committed
489 490
  }

491
  @override
492
  double computeMinIntrinsicHeight(double width) {
Hixie's avatar
Hixie committed
493
    if (child == null)
494 495
      return 0.0;
    return getIntrinsicDimensions(width: width).height;
Hixie's avatar
Hixie committed
496 497
  }

498
  @override
499
  double computeMaxIntrinsicHeight(double width) {
Hixie's avatar
Hixie committed
500
    if (child == null)
501 502
      return 0.0;
    return getIntrinsicDimensions(width: width).height;
Hixie's avatar
Hixie committed
503 504
  }

505
  Size getIntrinsicDimensions({
506
    double width = double.infinity,
507
    double height = double.infinity,
508
  }) {
Hixie's avatar
Hixie committed
509 510
    assert(child is RenderSector);
    assert(child.parentData is SectorParentData);
511 512
    assert(width != null);
    assert(height != null);
513 514 515
    if (!width.isFinite && !height.isFinite)
      return Size.zero;
    final double maxChildDeltaRadius = math.max(0.0, math.min(width, height) / 2.0 - innerRadius);
516
    final SectorDimensions childDimensions = child.getIntrinsicDimensions(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius);
517
    final double dimension = (innerRadius + childDimensions.deltaRadius) * 2.0;
518
    return Size.square(dimension);
Hixie's avatar
Hixie committed
519 520
  }

521
  @override
Hixie's avatar
Hixie committed
522
  void performLayout() {
523
    if (child == null || (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight)) {
Hixie's avatar
Hixie committed
524
      size = constraints.constrain(Size.zero);
525
      child?.layout(SectorConstraints(maxDeltaRadius: innerRadius), parentUsesSize: true);
526
      return;
Hixie's avatar
Hixie committed
527
    }
528 529 530 531 532
    assert(child is RenderSector);
    assert(child.parentData is SectorParentData);
    final double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius;
    child.parentData.radius = innerRadius;
    child.parentData.theta = 0.0;
533
    child.layout(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true);
534
    final double dimension = (innerRadius + child.deltaRadius) * 2.0;
535
    size = constraints.constrain(Size(dimension, dimension));
Hixie's avatar
Hixie committed
536 537
  }

538
  @override
Hixie's avatar
Hixie committed
539 540 541
  void paint(PaintingContext context, Offset offset) {
    super.paint(context, offset);
    if (child != null) {
542
      final Rect bounds = offset & size;
Hixie's avatar
Hixie committed
543
      // we move the offset to the center of the circle for the RenderSectors
544
      context.paintChild(child, bounds.center);
Hixie's avatar
Hixie committed
545 546 547
    }
  }

548
  @override
549
  bool hitTest(BoxHitTestResult result, { Offset position }) {
Hixie's avatar
Hixie committed
550 551
    if (child == null)
      return false;
552 553
    double x = position.dx;
    double y = position.dy;
Hixie's avatar
Hixie committed
554
    // translate to our origin
555 556
    x -= size.width / 2.0;
    y -= size.height / 2.0;
Hixie's avatar
Hixie committed
557
    // convert to radius/theta
558
    final double radius = math.sqrt(x * x + y * y);
559
    final double theta = (math.atan2(x, -y) - math.pi / 2.0) % kTwoPi;
Hixie's avatar
Hixie committed
560 561 562 563 564 565
    if (radius < innerRadius)
      return false;
    if (radius >= innerRadius + child.deltaRadius)
      return false;
    if (theta > child.deltaTheta)
      return false;
566
    child.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta);
567
    result.add(BoxHitTestEntry(this, position));
Hixie's avatar
Hixie committed
568 569 570 571 572 573
    return true;
  }

}

class RenderSolidColor extends RenderDecoratedSector {
574 575
  RenderSolidColor(
    this.backgroundColor, {
576
    this.desiredDeltaRadius = double.infinity,
577
    this.desiredDeltaTheta = kTwoPi,
578
  }) : super(BoxDecoration(color: backgroundColor));
Hixie's avatar
Hixie committed
579 580 581 582 583

  double desiredDeltaRadius;
  double desiredDeltaTheta;
  final Color backgroundColor;

584
  @override
Hixie's avatar
Hixie committed
585
  SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
586
    return SectorDimensions.withConstraints(constraints, deltaTheta: desiredDeltaTheta);
Hixie's avatar
Hixie committed
587 588
  }

589
  @override
Hixie's avatar
Hixie committed
590 591 592 593 594
  void performLayout() {
    deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
    deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
  }

595
  @override
Ian Hickson's avatar
Ian Hickson committed
596 597
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    if (event is PointerDownEvent) {
598
      decoration = const BoxDecoration(color: Color(0xFFFF0000));
Ian Hickson's avatar
Ian Hickson committed
599
    } else if (event is PointerUpEvent) {
600
      decoration = BoxDecoration(color: backgroundColor);
Ian Hickson's avatar
Ian Hickson committed
601
    }
Hixie's avatar
Hixie committed
602 603
  }
}
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628

/// The result of performing a hit test on [RenderSector]s.
class SectorHitTestResult extends HitTestResult {
  /// Creates an empty hit test result for hit testing on [RenderSector].
  SectorHitTestResult() : super();

  /// Wraps `result` to create a [HitTestResult] that implements the
  /// [SectorHitTestResult] protocol for hit testing on [RenderSector]s.
  ///
  /// This method is used by [RenderObject]s that adapt between the
  /// [RenderSector]-world and the non-[RenderSector]-world to convert a (subtype of)
  /// [HitTestResult] to a [SectorHitTestResult] for hit testing on [RenderSector]s.
  ///
  /// The [HitTestEntry]s added to the returned [SectorHitTestResult] are also
  /// added to the wrapped `result` (both share the same underlying data
  /// structure to store [HitTestEntry]s).
  ///
  /// See also:
  ///
  ///  * [HitTestResult.wrap], which turns a [SectorHitTestResult] back into a
  ///    generic [HitTestResult].
  SectorHitTestResult.wrap(HitTestResult result) : super.wrap(result);

  // TODO(goderbauer): Add convenience methods to transform hit test positions
  //    once we have RenderSector implementations that move the origin of their
629
  //    children (e.g. RenderSectorTransform analogs to RenderTransform).
630 631 632 633 634 635 636 637 638 639 640 641 642
}

/// A hit test entry used by [RenderSector].
class SectorHitTestEntry extends HitTestEntry {
  /// Creates a box hit test entry.
  ///
  /// The [radius] and [theta] argument must not be null.
  SectorHitTestEntry(RenderSector target, { @required this.radius,  @required this.theta })
      : assert(radius != null),
        assert(theta != null),
        super(target);

  @override
643
  RenderSector get target => super.target as RenderSector;
644 645 646 647 648 649 650 651 652

  /// The radius component of the hit test position in the local coordinates of
  /// [target].
  final double radius;

  /// The theta component of the hit test position in the local coordinates of
  /// [target].
  final double theta;
}